DEV Community

shashank ms
shashank ms

Posted on

Introduction to LLM Scientific Computing

We are going to build a scientific computing agent that reads a plain English physics or math problem, writes Python to solve it, executes that Python safely, and returns a formatted answer. This is useful for engineers and researchers who want reproducible calculations without manually context switching between a browser and a local REPL. We will run the agent against Oxlo.ai, which uses flat per-request pricing, so long problem descriptions and multi-turn reasoning chains do not inflate the cost.

What you'll need

Step 1: Initialize the Oxlo.ai client

I start every project by verifying the API client works. Create a file named scientific_agent.py and point the OpenAI SDK at Oxlo.ai. Because Oxlo.ai has no cold starts on popular models, the first request returns as fast as any subsequent one.

from openai import OpenAI

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

response = client.chat.completions.create(
    model="kimi-k2.6",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Say 'Oxlo.ai client is ready'"},
    ],
)

print(response.choices[0].message.content)

Step 2: Define the system prompt

The system prompt is the agent's job description. It tells the model to emit exactly one Python block, to use the standard library math module, and to print results with units. I keep it in a module-level constant so I can iterate on wording without touching the request logic.

from openai import OpenAI

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

SYSTEM_PROMPT = """You are a scientific computing assistant. Your goal is to solve quantitative problems accurately.

Rules:
1. Briefly explain your approach.
2. Write one Python code block inside triple backticks that solves the problem.
3. Use only the Python standard library and the `math` module.
4. Print the final answer with clear units.
5. State assumptions if the problem is under-specified.

Output format:
Approach: [brief text]



```python
import math
# calculations
print(f"Result: {value} units")
```


"""

response = client.chat.completions.create(
    model="kimi-k2.6",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": "Calculate the terminal velocity of a 2 mm diameter raindrop in air at 20 C using Stokes law."},
    ],
)

print(response.choices[0].message.content)

Step 3: Add a sandboxed code executor

We need to run the model's Python without trusting it completely. I use exec with a restricted globals dictionary and capture stdout so the agent's prints become data I can feed back into the conversation.

import contextlib
import io
import re
import traceback
from openai import OpenAI

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

SYSTEM_PROMPT = """You are a scientific computing assistant. Your goal is to solve quantitative problems accurately.

Rules:
1. Briefly explain your approach.
2. Write one Python code block inside triple backticks that solves the problem.
3. Use only the Python standard library and the `math` module.
4. Print the final answer with clear units.
5. State assumptions if the problem is under-specified.

Output format:
Approach: [brief text]



```python
import math
# calculations
print(f"Result: {value} units")
```


"""

def extract_python_block(text: str) -> str:
    match = re.search(r"

```python\s*(.*?)```

", text, re.DOTALL)
    if match:
        return match.group(1).strip()
    return text.strip()

def run_generated_code(code: str) -> dict:
    output_buffer = io.StringIO()
    result = {"stdout": "", "stderr": "", "success": False}
    restricted_globals = {
        "__builtins__": {
            "print": print,
            "range": range,
            "len": len,
            "abs": abs,
            "round": round,
            "pow": pow,
            "sum": sum,
            "min": min,
            "max": max,
        },
        "math": __import__("math"),
    }
    try:
        with contextlib.redirect_stdout(output_buffer):
            exec(code, restricted_globals)
        result["stdout"] = output_buffer.getvalue()
        result["success"] = True
    except Exception:
        result["stderr"] = traceback.format_exc()
    return result

response = client.chat.completions.create(
    model="kimi-k2.6",
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": "Calculate the terminal velocity of a 2 mm diameter raindrop in air at 20 C using Stokes law."},
    ],
)

raw_output = response.choices[0].message.content
code = extract_python_block(raw_output)
execution = run_generated_code(code)

print("--- GENERATED CODE ---")
print(code)
print("--- STDOUT ---")
print(execution["stdout"])
print("--- STDERR ---")
print(execution["stderr"])

Step 4: Close the loop with multi-turn reasoning

Raw stdout is not a great user experience. We feed the execution result back to the model and ask it to write a clean final answer. This second turn costs the same flat per-request rate as the first, which is why Oxlo.ai fits agentic workloads well: adding more context does not raise the price.

import contextlib
import io
import re
import traceback
from openai import OpenAI

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

SYSTEM_PROMPT = """You are a scientific computing assistant. Your goal is to solve quantitative problems accurately.

Rules:
1. Briefly explain your approach.
2. Write one Python code block inside triple backticks that solves the problem.
3. Use only the Python standard library and the `math` module.
4. Print the final answer with clear units.
5. State assumptions if the problem is under-specified.

Output format:
Approach: [brief text]



```python
import math
# calculations
print(f"Result: {value} units")
```


"""

FINAL_FORMAT_PROMPT = (
    "Based on the execution output above, write a concise final answer for the user. "
    "Include the numeric result, units, and a short interpretation. Do not write code."
)

def extract_python_block(text: str) -> str:
    match = re.search(r"

```python\s*(.*?)```

", text, re.DOTALL)
    if match:
        return match.group(1).strip()
    return text.strip()

def run_generated_code(code: str) -> dict:
    output_buffer = io.StringIO()
    result = {"stdout": "", "stderr": "", "success": False}
    restricted_globals = {
        "__builtins__": {
            "print": print,
            "range": range,
            "len": len,
            "abs": abs,
            "round": round,
            "pow": pow,
            "sum": sum,
            "min": min,
            "max": max,
        },
        "math": __import__("math"),
    }
    try:
        with contextlib.redirect_stdout(output_buffer):
            exec(code, restricted_globals)
        result["stdout"] = output_buffer.getvalue()
        result["success"] = True
    except Exception:
        result["stderr"] = traceback.format_exc()
    return result

def solve_problem(user_message: str) -> str:
    # Turn 1: generate code
    response1 = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
        ],
    )
    raw_output = response1.choices[0].message.content
    code = extract_python_block(raw_output)

    # Execute
    execution = run_generated_code(code)
    execution_summary = f"Execution stdout:\n{execution['stdout']}\nExecution stderr:\n{execution['stderr']}"

    # Turn 2: synthesize final answer
    response2 = client.chat.completions.create(
        model="kimi-k2.6",
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_message},
            {"role": "assistant", "content": raw_output},
            {"role": "user", "content": execution_summary + "\n\n" + FINAL_FORMAT_PROMPT},
        ],
    )
    return response2.choices[0].message.content

if __name__ == "__main__":
    query = "Calculate the terminal velocity of a 2 mm diameter raindrop in air at 20 C using Stokes law."
    print(solve_problem(query))

Step 5: Add error recovery

If the generated code raises an exception, we send the traceback back to the model and ask for a corrected block. This makes the agent robust against simple syntax or logic errors without crashing the whole pipeline.

import contextlib
import io
import re
import traceback
from openai import OpenAI

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

SYSTEM_PROMPT = """You are a scientific computing assistant. Your goal is to solve quantitative problems accurately.

Rules:
1. Briefly explain your approach.
2. Write one Python code block inside triple backticks that solves the problem.
3. Use only the Python standard library and the `math` module.
4. Print the final answer with clear units.
5. State assumptions if the problem is under-specified.

Output format:
Approach: [brief text]



```python
import math
# calculations
print(f"Result: {value} units")
```


"""

FINAL_FORMAT_PROMPT = (
    "Based on the execution output above, write a concise final answer for the user. "
    "Include the numeric result, units, and a short interpretation. Do not write code."
)

ERROR_CORRECTION_PROMPT = (
    "The previous code produced an error. Please correct the code and return a complete, runnable Python block. "
    "Preserve the original approach unless it is fundamentally flawed."
)

def extract_python_block(text: str) -> str:
    match = re.search(r"

```python\s*(.*?)```

", text, re.DOTALL)
    if match:
        return match.group(1).strip()
    return text.strip()

def run_generated_code(code: str) -> dict:
    output_buffer = io.StringIO()
    result = {"stdout": "", "stderr": "", "success": False}
    restricted_globals = {
        "__builtins__": {
            "print": print,
            "range": range,
            "len": len,
            "abs": abs,
            "round": round,
            "pow": pow,
            "sum": sum,
            "min": min,
            "max": max,
        },
        "math": __import__("math"),
    }
    try:
        with contextlib.redirect_stdout(output_buffer):
            exec(code, restricted_globals)
        result["stdout"] = output_buffer.getvalue()
        result["success"] = True
    except Exception:
        result["stderr"] = traceback.format_exc()
    return result

def solve_problem(user_message: str, max_retries: int = 1) -> str:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": user_message},
    ]

    # Turn 1: generate code
    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=messages,
    )
    assistant_content = response.choices[0].message.content
    messages.append({"role": "assistant", "content": assistant_content})

    code = extract_python_block(assistant_content)
    execution = run_generated_code(code)

    # Retry loop on error
    retries = 0
    while not execution["success"] and retries < max_retries:
        error_msg = f"Error:\n{execution['stderr']}\n\n{ERROR_CORRECTION_PROMPT}"
        messages.append({"role": "user", "content": error_msg})

        response = client.chat.completions.create(
            model="kimi-k2.6",
            messages=messages,
        )
        assistant_content = response.choices[0].message.content
        messages.append({"role": "assistant", "content": assistant_content})

        code = extract_python_block(assistant_content)
        execution = run_generated_code(code)
        retries += 1

    if not execution["success"]:
        return f"Failed after {retries} retries. Last error:\n{execution['stderr']}"

    # Turn final: synthesize answer
    execution_summary = f"Execution stdout:\n{execution['stdout']}"
    messages.append({"role": "user", "content": execution_summary + "\n\n" + FINAL_FORMAT_PROMPT})

    response = client.chat.completions.create(
        model="kimi-k2.6",
        messages=messages,
    )
    return response.choices[0].message.content

if __name__ == "__main__":
    query = "Calculate the terminal velocity of a 2 mm diameter raindrop in air at 20 C using Stokes law."
    print(solve_problem(query))

Run it

Save the final script as scientific_agent.py, export your key, and run it from your terminal.

export OXLO_API_KEY="sk-..."
python scientific_agent.py

Example output (abridged):

Approach: Stokes law gives terminal velocity v = (2/9) * (rho_p - rho_f) * g * r^2 / mu. I assume the raindrop is water (rho_p = 1000 kg/m3), air density at 20 C is about 1.204 kg/m3, and air dynamic viscosity is 1.81e-5 Pa.s.

Result: The terminal velocity is approximately 12.1 m/s. This is in the typical range for small raindrops, though Stokes law strictly applies to laminar flow with Reynolds number below about 1. In reality, a 2 mm drop may experience some turbulent drag, so the actual velocity would be slightly lower.

Next steps

Swap in deepseek-r1-671b for problems that need step by step symbolic reasoning before code generation, or add matplotlib to the sandbox and system prompt so the agent can return plots alongside numeric answers. If you plan to run this as a service, replace the local exec sandbox with a restricted Docker container or a subprocess timeout for stronger isolation.

Top comments (0)