Build a Local AI SOC Analyst

Analyze security logs with a local LLM that never sends data to the cloud.

Introduction

30 Second Summary

Every time you check your bank's app or log into a website, security systems record what happened. Somewhere, a security analyst has to sift through those records to find the break-in attempts hiding among millions of normal events.

In this project, you will build a local SOC analyst tool powered by Ollama. You feed in security log entries, and the AI classifies each one with a severity rating, threat type, and recommended next steps.

What You'll Build

Run your script, watch a local LLM analyze five security log entries one by one, and find a JSON incident report with the full results waiting on your machine.

By the end of this project, you'll have:

  • A local AI pipeline where you feed a raw security log into Ollama and watch it return a real-time threat assessment without any data leaving your machine.
  • Structured output enforced by a Pydantic schema, so you can programmatically access the severity, threat type, indicators of compromise, and recommended actions from every analysis.
  • A batch log processor that analyzes multiple security events and writes a complete incident report to a JSON file with a severity summary.
  • Secret Mission: a log file reader and threat dashboard that ingests a real log file, counts findings by severity, and highlights the most critical threats.

Do I need cybersecurity experience?

No prior cybersecurity or AI experience is needed. This project walks you through every installation step, from Ollama to the Python libraries.

Before We Start

Before diving in, take a moment to lock in what you're about to build and why it matters.

Set Up Ollama and Pull the Model

You have a clear goal: feed security logs into a local LLM and get structured threat analysis back. Before any of that can happen, you need the LLM itself running on your machine.

Ollama is a tool that lets you run large language models locally with simple terminal commands. In this step, you will install Ollama, download a model, and install the Python packages that connect your script to that model.

In this step, get ready to:
  • Install Ollama and verify it is running on your Windows machine.
  • Download the Llama 3.1 8B model to your local machine.
  • Install the Python packages your script will need.
Install Ollama on Windows

Ollama manages everything about running an LLM locally: downloading models, loading them into memory, and serving them through an API your code can talk to. You install it with a single PowerShell command.

  • Press the Windows key to open your search bar.
  • Type PowerShell and press Enter to open it.
  • Install Ollama by pasting this command into PowerShell and pressing Enter:
irm https://ollama.com/install.ps1 | iex

What does this command do?

This command downloads the Ollama installer script and runs it automatically. The install is completely silent. You will see progress messages like "Downloading Ollama..." and "Installing..." in your terminal.

After it finishes, Ollama starts running in the background and appears in your system tray (the small icons near your clock). No admin rights are required.

You should see the message Install complete. You can now run 'ollama'. in your terminal.

  • Verify the installation by running:
ollama --version

✔️ I see a version number

Ollama is installed and ready to go.

ⓧ Command not found

PowerShell may not have picked up the updated PATH. Close your PowerShell window completely and open a new one, then run ollama --version again.

If it still does not work, the install script may have failed silently. Re-run the install command above and watch for error messages.

What is Ollama?

Ollama is a tool for running LLMs on your own computer. It handles downloading model files, loading them into memory, and exposing them through a local API at http://localhost:11434.

For security work, running models locally means your log data never leaves your machine. No cloud provider sees your firewall rules, IP addresses, or authentication patterns.

Pull the Llama 3.1 8B model

Ollama is installed, but it does not come with any models. You need to download one before you can analyze anything.

  • Download the Llama 3.1 8B model by running:
ollama pull llama3.1:8b

What is this model?

Llama 3.1 8B is a general-purpose language model from Meta with 8 billion parameters. The download is about 4.9GB and uses Q4_K_M quantization, which compresses the model to fit comfortably in 16GB of RAM.

The model has a 128K context window, which is more than enough to analyze long log entries. On a CPU-only machine, expect the download to take a few minutes depending on your internet speed.

You will see a progress bar as the model downloads. Wait for it to complete before continuing.

Download seems stuck?

The 4.9GB download can take several minutes on slower connections. The progress bar updates in chunks, so it may appear frozen between updates. Give it time.

If the download fails partway through, run the same ollama pull llama3.1:8b command again. Ollama resumes from where it left off.

Help me troubleshoot my Ollama model download on Windows.

Install the Python dependencies

Your Python script will need two packages: the ollama library to talk to the local model, and pydantic to define structured output schemas.

  • Install both packages by running:
pip install ollama pydantic

What do these packages do?

  • ollama is the official Python library for communicating with your local Ollama instance. It provides a chat() function that sends messages to the model and returns responses.
  • pydantic is a data validation library. You will use it to define the exact JSON structure you want the LLM to return, so you get predictable fields instead of free-form text.

You should see pip download and install both packages along with their dependencies.

Seeing a permission error?

If pip reports a permission error, try running the command with the --user flag:

pip install ollama pydantic --user

Help me fix a pip install error on Windows.

Test the model with a quick prompt

Everything is installed. Time to confirm the model actually works by sending it a quick message.

Before you run the next command, what do you think will happen? You are sending the word "Hello" to an 8-billion-parameter language model running entirely on your CPU.

  • Test the model by running:
ollama run llama3.1:8b "Hello"

The model responds with a greeting. It may take 10-20 seconds on a CPU-only machine since the model needs to load into memory for the first time.

Why is the first response slow?

The first time you run a model, Ollama loads the full 4.9GB into RAM. Subsequent requests are much faster because the model stays loaded in memory.

On a CPU-only mini PC with 16GB RAM, expect roughly 5-15 tokens per second. Each log analysis later in the project will take about 15-30 seconds.

Model not responding?

Make sure Ollama is running in your system tray. Look for its icon near the clock in your taskbar. If you do not see it, search for Ollama in the Start menu and launch it.

If you see an error about the model not being found, confirm the pull completed by running ollama list. You should see llama3.1:8b in the output.

Help me get my Ollama model running on Windows.

Your local AI engine is live. You have Ollama running, a model loaded, and the Python packages ready to go. Next up, you will write your first Python script that sends a real security log entry to this model and sees what it thinks.

Send Your First Log to the LLM

Your local LLM is installed and ready to go. Now it's time to put it to work by sending it a real security log from a Python script.

The ollama Python library gives your scripts a direct connection to the model running on your machine. You'll use its chat() function to ask the model to analyze a suspicious log entry and see how it responds.

In this step, get ready to:
  • Create a Python script with sample security log entries.
  • Send a log entry to the local LLM and see how it responds.
Create the SOC analyst script

A SOC analyst's day starts with reading log entries from firewalls, web servers, and authentication systems. You'll create a script with three sample logs that represent common scenarios: a failed SSH login, a normal web request, and a blocked port scan.

  • Move to your Desktop by running this command in PowerShell:
cd ~/Desktop
  • Create a project folder and move into it by running these commands:
mkdir soc-analyst
cd soc-analyst
  • Confirm the folder exists by running:
ls

You should see the soc-analyst folder listed in your Desktop directory. The folder will be empty for now.

  • Create a new file called soc_analyst.py by running:
New-Item -Path "soc_analyst.py" -ItemType File
  • Open soc_analyst.py in a text editor by running:
notepad soc_analyst.py
  • Add the imports and sample log data by pasting this code into the file:
# Import the Ollama chat function and JSON module
import json
from ollama import chat

# Three sample security logs: a failed SSH login, a normal web request, and a blocked port scan
SAMPLE_LOGS = [
    'Aug 14 03:22:11 webserver sshd[4221]: Failed password for root from 203.0.113.45 port 22 ssh2 - attempt 47 of 50',
    'Aug 14 09:15:33 webserver nginx: 192.168.1.100 - - "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0"',
    'Aug 14 02:44:08 firewall kernel: [UFW BLOCK] IN=eth0 SRC=198.51.100.23 DST=10.0.0.5 PROTO=TCP SPT=44321 DPT=3389 LEN=44',
]

What does this code do?

import json loads Python's built-in JSON module. You'll use this in a later step to save results to a file.

from ollama import chat pulls in the function you'll use to talk to the local LLM.

SAMPLE_LOGS holds three real-world-style log entries. The first is a brute-force SSH attempt (attempt 47 of 50). The second is a perfectly normal web page load. The third is a firewall blocking an incoming connection on port 3389 (Remote Desktop).

Send the first log to the LLM

With the sample data ready, you can send the first log entry to the model and ask it to analyze the security implications.

  • Below the SAMPLE_LOGS list, add the code that sends the first log to the LLM and prints the response:
# Send the first log entry to the LLM for analysis
response = chat(
    model='llama3.1:8b',
    messages=[
        {'role': 'user', 'content': f'Analyze this security log entry:\n\n{SAMPLE_LOGS[0]}'}
    ],
)
print(response.message.content)

What does this code do?

chat() sends a message to the llama3.1:8b model running on your machine. The messages list works like a conversation. Here it contains a single user message asking the model to analyze the first log entry.

SAMPLE_LOGS[0] grabs the first log. That's the failed SSH login with 47 out of 50 password attempts.

response.message.content holds whatever the model wrote back. You print it straight to the terminal.

  • Save soc_analyst.py in Notepad (press Ctrl+S).

✔️ Awesome, I've got everything!

Great. Double check you've saved your file before moving on.

ⓧ I'd like to double check the full code

Here's the complete soc_analyst.py so far. Make sure your file matches this exactly:

import json
from ollama import chat

SAMPLE_LOGS = [
    'Aug 14 03:22:11 webserver sshd[4221]: Failed password for root from 203.0.113.45 port 22 ssh2 - attempt 47 of 50',
    'Aug 14 09:15:33 webserver nginx: 192.168.1.100 - - "GET /index.html HTTP/1.1" 200 1024 "-" "Mozilla/5.0"',
    'Aug 14 02:44:08 firewall kernel: [UFW BLOCK] IN=eth0 SRC=198.51.100.23 DST=10.0.0.5 PROTO=TCP SPT=44321 DPT=3389 LEN=44',
]

response = chat(
    model='llama3.1:8b',
    messages=[
        {'role': 'user', 'content': f'Analyze this security log entry:\n\n{SAMPLE_LOGS[0]}'}
    ],
)
print(response.message.content)
Run the script and observe the response

Before you run this, think about what you expect to get back. Will the model return a neat data structure you could feed into another program? Or something else?

  • Switch back to PowerShell and run your script:
python soc_analyst.py

Why is this taking a while?

Your mini PC is running the LLM entirely on CPU. Each analysis takes roughly 15 to 30 seconds depending on your processor. You'll see the response appear all at once when the model finishes generating.

You should see a multi-paragraph text response printed to your terminal. The model will talk about the SSH brute-force attempt, mention the source IP, discuss the severity, and recommend actions.

This analysis is genuinely useful for a human reading it. But now try to imagine writing code that extracts just the severity level from this wall of text. There's no consistent format, no guaranteed field names, and the model might phrase things differently every time you run it.

Why is this a problem?

A real SOC tool needs to sort threats by severity, count incidents by type, and trigger alerts automatically. Free-text output makes all of that impossible. You can't reliably run if severity == 'critical' on a paragraph of prose.

This is the gap you'll close in the next step.

Not seeing any output?

  • Make sure Ollama is running. Look for the llama icon in your system tray (bottom-right of your taskbar). If it's not there, search for Ollama in your Start menu and launch it.
  • Check that you saved soc_analyst.py after pasting both code blocks. Unsaved changes in Notepad won't run.
  • Confirm your PowerShell is in the soc-analyst folder. Run ls and check that soc_analyst.py appears in the listing.

Help me debug why my soc_analyst.py script isn't producing output from Ollama.

Your script can talk to the LLM, and the model clearly understands security logs. The missing piece is getting that analysis back in a format your code can actually work with. In the next step, you'll force the model to respond with structured JSON fields you can parse, sort, and act on.

Force Structured JSON Output

In the last step, your script sent a log entry to the local LLM and got back a wall of free-form text. A human can read that analysis and extract useful insights from it.

Your Python code cannot. Try to imagine writing code that reliably extracts the severity or threat type from a paragraph of unpredictable prose. A real SOC tool needs machine-readable output. In this step, you'll use Pydantic to define an exact schema and force the LLM to respond in that format every time.

In this step, get ready to:
  • Define a ThreatAnalysis schema that describes the exact fields you want back from the LLM.
  • Update the chat call to enforce structured JSON output using the schema.
  • Parse the structured response and access each field independently.
Define the ThreatAnalysis schema and system prompt

To produce structured output, the LLM needs two things: a schema defining the exact fields to return, and a system prompt telling it how to fill those fields. You'll define both now.

  • In soc_analyst.py, add this import on a new line between import json and from ollama import chat:
from pydantic import BaseModel
  • Below from ollama import chat, add the ThreatAnalysis schema and the system prompt by pasting this code above SAMPLE_LOGS:
# Define the exact fields the LLM must return
class ThreatAnalysis(BaseModel):
    severity: str
    threat_type: str
    description: str
    indicators: list[str]
    recommended_actions: list[str]


# Tell the LLM how to act and what each field means
SYSTEM_PROMPT = """You are an expert SOC (Security Operations Center) analyst. 
Your job is to analyze security log entries and provide structured threat assessments.

For each log entry, determine:
- severity: one of "critical", "high", "medium", "low", or "informational"
- threat_type: the category of threat (e.g., "brute_force", "port_scan", "sql_injection", "privilege_escalation", "normal_activity")
- description: a brief explanation of what the log entry indicates
- indicators: specific suspicious elements found in the log (IPs, patterns, payloads)
- recommended_actions: concrete steps a SOC analyst should take next

Be precise and actionable. If the log entry appears benign, classify it as "informational" with threat_type "normal_activity"."""

What does this code do?

ThreatAnalysis is a Pydantic model that acts as a contract. It lists the five fields the LLM must include in every response: severity, threat_type, description, indicators, and recommended_actions.

SYSTEM_PROMPT is the instruction manual for the LLM. It tells the model to behave as a SOC analyst and explains what each severity level and threat type means. The more precise this prompt is, the more consistent the output becomes.

  • Save soc_analyst.py.

Why a system prompt instead of a longer user message?

System messages set the LLM's persona and rules before it sees any log data. The model treats system instructions as higher-priority context, so it follows them more reliably than instructions buried in a user message.

Update the chat call to enforce structured output

With the schema and prompt defined, it is time to wire them into the actual chat call. Ollama's format parameter accepts a JSON schema and forces the model to respond in that exact structure.

  • In soc_analyst.py, scroll down to the bottom of the file and find the existing chat() call:
response = chat(
    model='llama3.1:8b',
    messages=[
        {'role': 'user', 'content': f'Analyze this security log entry:\n\n{SAMPLE_LOGS[0]}'}
    ],
)
print(response.message.content)
  • Replace that entire block (from response = chat( down to print(response.message.content)) with the following:
# Send the log entry with the system prompt and enforce structured output
response = chat(
    model='llama3.1:8b',
    messages=[
        {'role': 'system', 'content': SYSTEM_PROMPT},
        {'role': 'user', 'content': f'Analyze this security log entry:\n\n{SAMPLE_LOGS[0]}'},
    ],
    format=ThreatAnalysis.model_json_schema(),
    options={'temperature': 0},
)
print(response.message.content)

What changed in the chat call?

  • A system message now comes first in the messages list, giving the LLM its SOC analyst persona before it sees any log data.
  • format=ThreatAnalysis.model_json_schema() converts your Pydantic class into a JSON schema and hands it to Ollama. The model is now forced to respond using exactly those fields.
  • options={'temperature': 0} makes the output deterministic. The model picks the most likely token at every step, so repeated runs produce consistent results.
  • Save soc_analyst.py and run it:
python soc_analyst.py

Instead of a free-text paragraph, you should see a raw JSON string printed to the terminal. It contains all five fields from your schema, but it is still one long unformatted string.

Seeing a validation error or empty output?

  • Make sure SYSTEM_PROMPT is placed above SAMPLE_LOGS in the file. The script reads top to bottom, so the prompt must be defined before the code that uses it.
  • Check that the triple-quoted string ends with """ on the last line. A missing closing triple-quote turns the rest of your file into part of the string.
  • If the response takes more than 30 seconds, that is normal on CPU. The model generates roughly 5-15 tokens per second without a GPU.

Still stuck? Ask for help.

Parse the structured response

The response is now a valid JSON string that matches your schema. The final step is to parse it back into a ThreatAnalysis object so you can access each field by name in your code.

  • In soc_analyst.py, find this line at the bottom of the file:
print(response.message.content)
  • Replace that line with the following code to parse the JSON and print each field:
# Parse the JSON response into a ThreatAnalysis object
analysis = ThreatAnalysis.model_validate_json(response.message.content)
print(f'Severity: {analysis.severity}')
print(f'Threat type: {analysis.threat_type}')
print(f'Description: {analysis.description}')
print(f'Indicators: {analysis.indicators}')
print(f'Recommended actions: {analysis.recommended_actions}')

What does model_validate_json do?

model_validate_json() takes a raw JSON string and converts it into a typed Python object. Instead of digging through a dictionary with string keys, you access fields like analysis.severity and analysis.indicators. If the JSON is missing a field or has the wrong type, Pydantic raises an error immediately.

Before you run this, what do you expect the output to look like compared to the raw JSON string you saw a moment ago?

  • Save soc_analyst.py and run it:
python soc_analyst.py

This time, instead of a raw JSON blob, you should see five clean lines printed to the terminal. Each field has its own label and value.

What should I see?

Your output will look something like this (exact wording varies by run):

Severity: critical
Threat type: brute_force
Description: Multiple failed SSH login attempts for root...
Indicators: ['203.0.113.45', 'port 22', 'attempt 47 of 50']
Recommended actions: ['Block IP 203.0.113.45 at the firewall', ...]

The key thing to notice: every field you defined in ThreatAnalysis is now a predictable, named value you can work with in code.

Output still looks like raw JSON?

  • Make sure you deleted the old print(response.message.content) line. If both the old print and the new parsed prints exist, you will see the raw JSON followed by the parsed fields.
  • Check that analysis = ThreatAnalysis.model_validate_json(response.message.content) appears right after the closing ) of the chat() call.

Need more help debugging?

✔️ Awesome, I've got everything!

Great. Double check you have saved soc_analyst.py before moving on.

ⓧ I'd like to double check the full code

The full file is over 30 lines, so scroll through your soc_analyst.py and confirm these sections are present, in this order:

  • Three imports at the top: import json, from pydantic import BaseModel, from ollama import chat.
  • The ThreatAnalysis class with five fields: severity, threat_type, description, indicators, recommended_actions.
  • SYSTEM_PROMPT triple-quoted string starting with "You are an expert SOC" and ending with normal_activity".
  • SAMPLE_LOGS list with three log entries.
  • The chat() call with a system message, a user message, format=ThreatAnalysis.model_json_schema(), and options={'temperature': 0}.
  • analysis = ThreatAnalysis.model_validate_json(response.message.content) followed by five print() calls, one per field.

Your script now gets clean, predictable data from every log analysis. Each field is accessible by name, ready for code to act on. Next up, you'll scale this to process a batch of log entries and generate a full incident report.

Analyze a Batch of Logs and Generate an Incident Report

Your structured output pipeline proved that the LLM can return clean, parseable JSON for a single log entry. Each field landed exactly where your Pydantic model expected it.

A real SOC analyst processes dozens of log entries and produces reports summarizing the threat landscape. In this step, you'll refactor the script into reusable functions, feed five sample logs through the LLM, and write the results to a JSON incident report file.

In this step, get ready to:
  • Wrap the analysis logic in a reusable analyze_log() function.
  • Process all five sample log entries through the LLM in a loop.
  • Generate a structured incident report and save it to incident_report.json.
Add new log entries and create the analysis functions

Your script currently has three sample log entries covering an SSH brute force, a normal web request, and a firewall block. A thorough security analysis needs more variety. You'll add two new entries and extract the analysis logic into reusable functions.

  • In soc_analyst.py, find your SAMPLE_LOGS list.
  • Add these two new entries at the end of the list, right after the firewall log entry and before the closing ]:
    # Privilege escalation - someone sudo'd to root
    'Aug 14 06:30:55 webserver sudo: admin : TTY=pts/0 ; PWD=/root ; USER=root ; COMMAND=/bin/bash',
    # SQL injection attempt via sqlmap
    'Aug 14 11:02:19 webserver nginx: 45.33.32.156 - - "GET /login?user=admin\'%20OR%201=1-- HTTP/1.1" 200 3842 "-" "sqlmap/1.7"',

What do these new log entries capture?

The sudo entry shows someone escalating privileges to the root user. This could be a legitimate admin action or a sign of compromise.

The nginx entry is a classic SQL injection attempt. The '%20OR%201=1-- payload and sqlmap user agent are dead giveaways of an automated attack tool probing for vulnerabilities.

  • Now delete everything below the SAMPLE_LOGS list. Remove from response = chat( all the way down to the last print(...) statement.
  • Replace that deleted code with these two functions:
def analyze_log(log_entry: str) -> ThreatAnalysis:
    """Send a single log entry to the local LLM for analysis."""
    # Send the log to the LLM with the SOC analyst system prompt
    response = chat(
        model='llama3.1:8b',
        messages=[
            {'role': 'system', 'content': SYSTEM_PROMPT},
            {'role': 'user', 'content': f'Analyze this security log entry:\n\n{log_entry}'},
        ],
        format=ThreatAnalysis.model_json_schema(),
        options={'temperature': 0},
    )
    # Parse and return a structured ThreatAnalysis object
    return ThreatAnalysis.model_validate_json(response.message.content)

def generate_report(results: list[dict]) -> dict:
    """Build an incident report with summary statistics."""
    # Count how many entries fall into each severity level
    severity_counts = {}
    for result in results:
        level = result['analysis']['severity']
        severity_counts[level] = severity_counts.get(level, 0) + 1

    return {
        'report_title': 'SOC Incident Report',
        'total_entries': len(results),
        'summary': severity_counts,
        'entries': results,
    }

What do these functions do?

analyze_log() wraps the same chat() logic you wrote in Step 3 into a reusable function. It accepts any log entry string, sends it to the LLM with the system prompt and structured output schema, and returns a parsed ThreatAnalysis object.

generate_report() takes the collected analysis results and builds a summary dictionary. It loops through every result and counts how many entries fell into each severity level using severity_counts.get(level, 0) + 1. The returned dictionary bundles the full entries with the severity breakdown.

Run the batch analysis and generate the report

With the analysis and report functions ready, you need one more piece. A main() function that loops through every sample log, feeds each one to the LLM, and writes the results to a file.

  • Add the main() function and entry point below generate_report():
def main():
    print('=== Local AI SOC Analyst ===\n')
    print(f'Analyzing {len(SAMPLE_LOGS)} log entries with Llama 3.1:8b...\n')

    # Send each log to the LLM and collect results
    results = []
    for i, log in enumerate(SAMPLE_LOGS, 1):
        print(f'[{i}/{len(SAMPLE_LOGS)}] Analyzing: {log[:80]}...')
        analysis = analyze_log(log)
        print(f'  Severity: {analysis.severity.upper()}')
        print(f'  Threat:   {analysis.threat_type}')
        print(f'  Action:   {analysis.recommended_actions[0] if analysis.recommended_actions else "None"}\n')
        results.append({
            'log_entry': log,
            'analysis': analysis.model_dump(),
        })

    # Build the report and save to JSON
    report = generate_report(results)
    with open('incident_report.json', 'w') as f:
        json.dump(report, f, indent=2)

    print('=== Report Summary ===')
    for severity, count in report['summary'].items():
        print(f'  {severity.upper()}: {count}')
    print(f'\nFull report saved to incident_report.json')

if __name__ == '__main__':
    main()

What does the main function do?

main() loops through every entry in SAMPLE_LOGS, calling analyze_log() on each one. For each log, it prints a progress line with the severity, threat type, and first recommended action.

After all logs are processed, it calls generate_report() to build the summary, writes the full report to incident_report.json with json.dump(), and prints the severity counts to the terminal.

model_dump() converts each ThreatAnalysis object into a plain dictionary so it can be serialized to JSON.

  • Save soc_analyst.py.

Two of your five logs are clearly malicious (SSH brute force, SQL injection). One is a normal web request. Two are ambiguous (port scan, sudo to root). Before you run, what severity ratings do you expect the model to assign to each?

  • Run the batch analysis (this takes 1-3 minutes on CPU):
python soc_analyst.py

Each log entry is analyzed one by one, with a severity, threat type, and recommended action printed as it completes. At the end, a Report Summary shows the count per severity level.

Why is each log taking so long?

The LLM runs entirely on your CPU. Each log analysis generates roughly 200 tokens of output at 5-15 tokens per second, so each entry takes about 15-30 seconds. Processing all 5 entries back to back means the full batch takes 1-3 minutes total.

  • Confirm the report file was created by running:
ls incident_report.json

You should see incident_report.json listed in the output. This file contains the full structured analysis for all five log entries plus the severity summary.

Script not running or no output?

  • If you see Failed to connect to Ollama, Ollama is not running. Check your system tray for the Ollama icon and relaunch it from the Start menu if needed.
  • If you see NameError, make sure both analyze_log() and generate_report() are defined above main() in your file. Python reads top to bottom, so functions must be defined before they are called.
  • If you see IndentationError, check that every line inside each function is indented with exactly 4 spaces. Mixing tabs and spaces causes this error.

Still stuck? Help me debug my soc_analyst.py batch analysis script.

✔️ Awesome, I've got everything!

Great. Double check you've saved soc_analyst.py and that incident_report.json exists in your project folder.

ⓧ I'd like to double check the full code

Scroll through soc_analyst.py and confirm these sections are present, in this order:

  • Imports: json, BaseModel from pydantic, chat from ollama.
  • ThreatAnalysis class with five fields: severity, threat_type, description, indicators, recommended_actions.
  • SYSTEM_PROMPT string instructing the LLM to act as a SOC analyst.
  • SAMPLE_LOGS list with five entries (SSH brute force, normal HTTP GET, firewall block, sudo to root, SQL injection).
  • analyze_log() function that sends a single log entry to the LLM and returns a ThreatAnalysis object.
  • generate_report() function that builds the incident report dictionary with severity counts.
  • main() function that loops through all logs, prints progress, builds the report, and writes it to incident_report.json.
  • if __name__ == '__main__': entry point calling main().

Your SOC analyst tool now processes a full batch of logs and produces a structured incident report. Next up, you'll push this tool further in the Secret Mission by reading real log files and building a threat dashboard.

Secret mission

Build a Log File Reader with Threat Dashboard

Your SOC analyst tool currently only works with hardcoded sample logs. Can you upgrade it to read real log files and display a threat severity dashboard that highlights the most critical findings?

Clean Up Your Resources

Clean Up Your Resources

Decide whether to keep your resources running, pause them to come back later, or delete them entirely. This project runs entirely locally, so there are no ongoing costs.

Resources you used:

  • Ollama application (installed on Windows).
  • Llama 3.1:8b model (about 4.9GB on disk).
  • Python packages: ollama and pydantic.
  • Project files: soc_analyst.py and incident_report.json.

Keep everything running

No action needed. Choose this if you plan to keep experimenting with your SOC analyst tool or want to try analyzing different log files.

  • Ollama sits in your system tray and uses minimal resources when idle.
  • The downloaded Llama 3.1:8b model stays on disk (about 4.9GB).
  • Your soc_analyst.py script and incident_report.json file remain on your machine ready to use.

Pause - I'll come back to this later

Close Ollama to free up memory. The model stays downloaded so you can pick up where you left off.

  • Right-click the Ollama icon in your system tray (bottom-right of your taskbar) and select Quit Ollama.
  • The Llama 3.1:8b model stays on disk. When you are ready to resume, relaunch Ollama from the Start menu.

Delete - I don't want to use this again

Remove all project resources and reclaim disk space. Follow each step in order.

  • Remove the downloaded model by running this command in PowerShell:
ollama rm llama3.1:8b
  • Uninstall Ollama through Windows Settings. Press the Windows key, type Settings, and press Enter.
  • Select Apps in the left sidebar, then click Installed apps.
  • Find Ollama in the list, click the three-dot menu next to it, and select Uninstall. The uninstaller will ask whether you want to remove downloaded models. Choose to remove them.
  • Uninstall the Python packages by running:
pip uninstall ollama pydantic -y
  • Delete your project files by running:
del soc_analyst.py
del incident_report.json

Nice Work!

Nice Work!

You just built a fully local, privacy-first AI security analyst. Your tool takes raw log entries, feeds them to a local LLM through Ollama, and produces structured threat intelligence that a real SOC team could act on. No cloud API keys, no data leaving your machine.

You've learned how to:

  • Installed Ollama and connected Python to a local Llama 3.1 8B model to send security logs for AI-powered analysis.
  • Discovered the limitation of free-text LLM responses and solved it with Pydantic-enforced structured JSON output, giving you machine-readable threat classifications.
  • Built a batch log processor that analyzes multiple security events and generates a complete JSON incident report with severity summaries.
  • Secret Mission: Extended the tool to read real log files from the command line and display a formatted threat severity dashboard.

Ready to quiz yourself?