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

Book a call →
Back to Blogs
Learn AI

Introduction to LLM Scientific Computing

We are going to build a natural-language scientific computing agent that reads plain English physics and math problems, generates executable Python, and...

Introduction to LLM Scientific Computing

We are going to build a natural-language scientific computing agent that reads plain English physics and math problems, generates executable Python, and returns exact numerical answers. It helps engineers, researchers, and students skip manual calculator work and produces reproducible scripts they can audit. The entire pipeline runs against Oxlo.ai's OpenAI-compatible API, so we can iterate on prompts and model choices without touching any client configuration.

What you'll need

  • Python 3.10 or newer installed locally.
  • The OpenAI Python SDK installed via pip install openai. Oxlo.ai exposes a fully OpenAI-compatible endpoint, so this is the only client library we need.
  • An API key from https://portal.oxlo.ai. Oxlo.ai uses flat per-request pricing, which means long system prompts and multi-turn error correction loops do not inflate your bill the way token-based metering would. You can review the exact tiers at https://oxlo.ai/pricing.
  • Optional: numpy if you later want to extend the runner to matrix algebra, but the standard library math and statistics modules are sufficient for this tutorial.

Step 1: Set up the Oxlo.ai client

Oxlo.ai requires only two changes to the standard OpenAI client setup: the base URL and the API key. I am using Qwen 3 32B here because it handles agentic workflows and multilingual reasoning particularly well, but the code is model-agnostic. You can drop in Llama 3.3 70B for general-purpose tasks, DeepSeek V3.2 for coding-heavy problems, or Kimi K2.6 for advanced reasoning without changing any other logic. Because Oxlo.ai charges one flat cost per request, you can experiment with different models for the same prompt and pay the same rate regardless of which weights are loaded. The client is instantiated once and reused across requests, which avoids unnecessary TCP handshakes and keeps latency low.

from openai import OpenAI

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

Step 2: Write the system prompt

The system prompt is the contract that keeps the model honest. Without strong constraints, an LLM will wander into conversational filler, installation instructions, or multiple disconnected code snippets. We want a rigid structure: a short explanation followed by exactly one fenced Python block. Keeping the output format predictable makes the downstream parser trivial and reduces failure modes. Notice that we explicitly restrict the model to the standard library. If we allowed arbitrary imports, the generated code might try to use libraries that are not installed in the execution environment, causing unnecessary runtime failures. By pinning the allowed modules to math and statistics, we align the model's output with the restricted runner we build in the next step.

SYSTEM_PROMPT = """You are a scientific computing engine. The user asks math, physics, or engineering questions.

Respond in exactly this order:
1. A brief explanation of the formulas or approach.
2. A single Python code block inside triple backticks labeled python. The code must print the final answer with clear labels.
Use only the Python standard library, specifically the math and statistics modules. Do not write installation instructions or example usage outside the code block."""

Step 3: Create the code extraction and runner

The model returns markdown, so we need a small parser that pulls out the fenced Python code. I then execute the code in a restricted namespace. I inject math and statistics, plus a curated set of safe builtins, so the generated script can run without importing anything exotic. This is not a full production sandbox, but it prevents accidental file system access and keeps the tutorial self-contained. I capture stdout so the printed results can be returned to the user cleanly. The regex r"```python\n(.*?)```" is intentionally greedy within the fence but non-greedy across the body, so it stops at the first closing fence. If you later want to support multiple code blocks, you can switch to findall, but for this agent a single block is the correct constraint. The env dictionary acts as both the global and local namespace for exec, which means variables defined inside the script persist only for that run.

import re
import math
import statistics
import io
import contextlib

def extract_and_run(text: str):
    pattern = r"```python\n(.*?)```"
    match = re.search(pattern, text, re.DOTALL)
    if not match:
        return None, "No Python code block found in the response."
    
    code = match.group(1)
    
    env = {
        "__builtins__": {
            "print": print,
            "len": len,
            "range": range,
            "abs": abs,
            "round": round,
            "pow": pow,
            "sum": sum,
            "max": max,
            "min": min,
            "float": float,
            "int": int,
        },
        "math": math,
        "statistics": statistics,
    }
    
    stdout = io.StringIO()
    try:
        with contextlib.redirect_stdout(stdout):
            exec(code, env)
    except Exception as e:
        return None, f"Runtime error: {e}"
    
    return stdout.getvalue(), None

Step 4: Build the solver function

Now we wire the client to the runner. The solve_problem function sends the user query to Oxlo.ai, extracts the code, and runs it. If the code throws an exception, we catch the traceback and feed it back to the model in a second request. This self-correction loop is where the project graduates from a simple wrapper to an agent. Because Oxlo.ai prices by the request, not by the token, adding a retry step is a predictable incremental cost. You will never get a surprise bill because the error message happened to be ten paragraphs long. The retry message includes both the original error and an explicit instruction to preserve the output format. This is important because the model must remember to wrap its corrected code in triple backticks. I set temperature=0.2 to keep the output deterministic; scientific computing benefits from consistency more than creativity. You could lower it further to 0.0 if you find the model introducing variability across identical prompts.

def solve_problem(user_message: str, model: str = "qwen-3-32b") -> str:
    # Initial generation
    response = client.chat.completions.create(
        model=model,
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
        temperature=0.2,
    )
    
    raw = response.choices[0].message.content
    output, error = extract_and_run(raw)
    
    # Retry once on failure
    if error:
        retry_message = (
            f"The previous code failed with this error:\n{error}\n\n"
            "Please correct the code and return a fixed version in the same format."
        )
        response = client.chat.completions.create(
            model=model,
            messages=[
                {"role": "system", "content": SYSTEM_PROMPT},
                {"role": "user", "content": user_message},
                {"role": "assistant", "content": raw},
                {"role": "user", "content": retry_message},
            ],
            temperature=0.2,
        )
        raw = response.choices[0].message.content
        output, error = extract_and_run(raw)
    
    if error:
        return f"Failed after retry.\nError: {error}\n\nRaw model output:\n{raw}"
    
    return f"=== Explanation ===\n{raw}\n\n=== Execution Output ===\n{output}"

Step 5: Add the entry point

Finally, we add a __main__ guard so we can run the agent from the command line with a concrete physics problem. Kinematics problems are a good stress test because they require symbolic rearrangement before numerical evaluation. The model must recognize that the final height is zero relative to the ground, set up the quadratic accordingly, and select the physically meaningful positive root. A weaker model might solve for time to apex and simply double it, which would be wrong here because the launch height is non-zero. This entry point is also where you would swap in different user queries or wire the solver to a web framework later.

if __name__ == "__main__":
    query = (
        "A ball is thrown straight upward at 25 m/s from a platform 10 m above the ground. "
        "Calculate the maximum height above ground and the total time until impact. "
        "Use g = 9.81 m/s^2."
    )
    print(solve_problem(query))

Run it

Save the complete script as science_agent.py and run python science_agent.py. The first request usually succeeds, but if the model flips a sign in the quadratic formula or forgets an import, the retry loop fires automatically. Because Oxlo.ai uses flat per-request pricing, that extra turn costs the same whether the error trace is ten lines or ten thousand. Check that the execution output section actually contains printed values. If the model only assigns variables without printing them, the stdout capture will be empty and the user will see a blank result. The system prompt explicitly requests print statements to avoid this, but the retry loop is your safety net. After a successful run, you should see output similar to this:

=== Explanation ===
We treat this as one-dimensional motion under constant gravity. First we find the time to reach maximum height using v = u + at, where final velocity is zero. Then we compute the apex height. For total flight time, we solve the quadratic equation 0 = h0 + ut - 0.5*g*t^2 for the positive root.

```python
import math

u = 25.0
h0 = 10.0
g = 9.81

# Time to maximum height
t_apex = u / g
h_max = h0 + u * t_apex - 0.5 * g * t_apex ** 2

# Quadratic coefficients for 0.5*g*t^2 - u*t - h0 = 0
a = 0.5 * g
b = -u
c = -h0
discriminant = b**2 - 4*a*c
t_total = (-b + math.sqrt(discriminant)) / (2*a)

print(f"Maximum height: {h_max:.2f} m")
print(f"Total flight time: {t_total:.2f} s")
```

=== Execution Output ===
Maximum height: 41.87 m
Total flight time: 5.47 s

Next steps

This agent works for anything that fits in a short Python script, but you can push it further. First, add SymPy to the execution namespace and update the system prompt to allow symbolic mathematics. This lets the agent return exact algebraic expressions instead of floating-point approximations, which is often what scientists actually need. Second, if you want to ingest large experimental logs or multi-page LaTeX derivations before asking questions, switch to DeepSeek V4 Flash. It offers a 1M context window and near state-of-the-art open-source reasoning, and on Oxlo.ai you still pay the same flat per-request rate even when that context is massive.

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.