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
- Python 3.10 or newer
- An Oxlo.ai API key from https://portal.oxlo.ai
- The OpenAI SDK:
pip install openai
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)