DEV Community

shashank ms
shashank ms

Posted on

Leveraging LLMs for Business Intelligence

I recently shipped a lightweight BI analyst for an operations team that lives in spreadsheets. Instead of standing up Looker or Tableau, I gave them a Python script that shoves a CSV export into an LLM and gets back a reasoned report with anomaly detection. In this tutorial we will build that exact tool against Oxlo.ai using the standard OpenAI SDK. No new infrastructure, no token-counting surprises.

What you'll need

Before we start, make sure you have the following ready:

  • Python 3.10 or newer
  • An Oxlo.ai API key from https://portal.oxlo.ai
  • The OpenAI SDK installed: pip install openai

Step 1: Configure the Oxlo.ai client

I always verify the client before writing business logic. Create a file named bi_agent.py and add the following. Oxlo.ai is a drop-in replacement for the OpenAI client, so the only change is the base_url.

from openai import OpenAI

client = OpenAI(
    base_url="https://api.oxlo.ai/v1",
    api_key="YOUR_OXLO_API_KEY",  # From https://portal.oxlo.ai
)

# Smoke test
ping = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[{"role": "user", "content": "Confirm connection"}],
)
print(ping.choices[0].message.content)

Step 2: Define the system prompt

The system prompt is the analyst's job description. I keep it rigid: cite exact numbers, show your work, and call out anomalies explicitly.

SYSTEM_PROMPT = """You are a senior business intelligence analyst examining raw CSV exports.

Rules:
- Base every claim on calculations from the provided data.
- Show your work when comparing aggregates.
- Flag anomalies by naming the specific row and explaining the deviation.
- If the data cannot answer the question, state that clearly.
- Format currency with two decimal places and use commas for thousands.
"""

Step 3: Prepare sample data

To keep the tutorial self-contained, I embed a small sales CSV directly in the script. In production I swap this for pd.read_csv, but a string means you can run the file immediately with no dependencies beyond the OpenAI SDK.

RAW_CSV = """date,region,product,units,revenue
2024-01-15,North,Widget-A,120,3600.00
2024-01-16,North,Widget-A,125,3750.00
2024-01-17,North,Widget-A,30,900.00
2024-01-15,South,Widget-B,80,2400.00
2024-01-16,South,Widget-B,82,2460.00
2024-01-17,South,Widget-B,85,2550.00
2024-01-15,East,Widget-A,200,6000.00
2024-01-16,East,Widget-A,210,6300.00
2024-01-17,East,Widget-A,205,6150.00
"""

Step 4: Build the analysis function

The core function prepends the CSV to the user question and sends the bundle to Oxlo.ai. I use llama-3.3-70b because it handles tabular reasoning well. Because Oxlo.ai charges a flat rate per request rather than per token, I do not have to worry about the cost spiking when I paste in a longer CSV. See https://oxlo.ai/pricing for current plan details.

def run_analysis(csv_text: str, question: str) -> str:
    user_message = f"DATA:\n{csv_text}\n\nQUESTION:\n{question}"

    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

Step 5: Add structured JSON output

Plain text is great for Slack, but dashboards need JSON. I add a second function that forces JSON mode via the OpenAI-compatible response_format parameter. Oxlo.ai supports this on all modern chat models, so the output is machine-readable without regex parsing.

import json

def run_structured_analysis(csv_text: str, question: str) -> dict:
    user_message = (
        f"DATA:\n{csv_text}\n\nQUESTION:\n{question}\n\n"
        "Respond with valid JSON only. Include keys: summary, top_region, "
        "anomaly_detected, anomaly_details, recommendation."
    )

    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.2,
        response_format={"type": "json_object"},
    )
    return json.loads(response.choices[0].message.content)

Run it

Here is the complete bi_agent.py assembled, plus the terminal output when I run it against the sample data.

from openai import OpenAI
import json

client = OpenAI(
    base_url="https://api.oxlo.ai/v1",
    api_key="YOUR_OXLO_API_KEY",
)

SYSTEM_PROMPT = """You are a senior business intelligence analyst examining raw CSV exports.

Rules:
- Base every claim on calculations from the provided data.
- Show your work when comparing aggregates.
- Flag anomalies by naming the specific row and explaining the deviation.
- If the data cannot answer the question, state that clearly.
- Format currency with two decimal places and use commas for thousands.
"""

RAW_CSV = """date,region,product,units,revenue
2024-01-15,North,Widget-A,120,3600.00
2024-01-16,North,Widget-A,125,3750.00
2024-01-17,North,Widget-A,30,900.00
2024-01-15,South,Widget-B,80,2400.00
2024-01-16,South,Widget-B,82,2460.00
2024-01-17,South,Widget-B,85,2550.00
2024-01-15,East,Widget-A,200,6000.00
2024-01-16,East,Widget-A,210,6300.00
2024-01-17,East,Widget-A,205,6150.00
"""

def run_analysis(csv_text: str, question: str) -> str:
    user_message = f"DATA:\n{csv_text}\n\nQUESTION:\n{question}"
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.2,
    )
    return response.choices[0].message.content

def run_structured_analysis(csv_text: str, question: str) -> dict:
    user_message = (
        f"DATA:\n{csv_text}\n\nQUESTION:\n{question}\n\n"
        "Respond with valid JSON only. Include keys: summary, top_region, "
        "anomaly_detected, anomaly_details, recommendation."
    )
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.2,
        response_format={"type": "json_object"},
    )
    return json.loads(response.choices[0].message.content)

if __name__ == "__main__":
    print("=== Freeform Report ===")
    q1 = "Which region had the highest total revenue, and did any day look like an anomaly? Show your work."
    print(run_analysis(RAW_CSV, q1))

    print("\n=== Structured Report ===")
    q2 = "Analyze the sales data and highlight any outliers."
    structured = run_structured_analysis(RAW_CSV, q2)
    print(json.dumps(structured, indent=2))

Running python bi_agent.py produces output similar to this:

=== Freeform Report ===
East region generated the highest total revenue at $18,450.00 across the three-day period.
North region on 2024-01-17 is an anomaly. Units dropped to 30, which is 76.0% below the prior two-day average of 122.5 units. Revenue fell to $900.00, pulling the North total down to $8,250.00.

=== Structured Report ===
{
  "summary": "East leads revenue with $18,450.00, followed by North at $8,250.00 and South at $7,410.00.",
  "top_region": "East",
  "anomaly_detected": true,
  "anomaly_details": "North on 2024-01-17: 30 units vs. prior avg 122.5, revenue $900.00 vs. prior avg $3,675.00.",
  "recommendation": "Investigate North inventory or reporting delay for January 17."
}

Wrap-up

That is the entire agent. Two directions I would take next. First, replace the inline CSV with a SQL tool using Oxlo.ai function calling so the agent queries a live warehouse instead of static exports. Second, wrap the structured JSON output in a scheduled GitHub Action or cron job that emails a morning report to stakeholders without anyone touching Python.

Top comments (0)