DEV Community

shashank ms
shashank ms

Posted on

Building a Virtual Assistant with LLM: A Step-by-Step Tutorial

We are going to build a command-line virtual assistant that remembers conversation context and uses tools to answer questions about time, math, and weather. It runs on Oxlo.ai's request-based API, so long system prompts and multi-turn reasoning do not inflate your bill. By the end you will have a single Python file you can extend with real APIs and additional models.

What you'll need

Step 1: Connect to Oxlo.ai

First we instantiate the OpenAI-compatible client pointing at Oxlo.ai. I use llama-3.3-70b here because it supports tool calling and has no cold starts.

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="llama-3.3-70b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Hello"},
    ],
)
print(response.choices[0].message.content)

Step 2: Write the system prompt

A strong system prompt keeps the assistant focused and tells it how to behave. Store it in a constant so you can iterate quickly.

SYSTEM_PROMPT = """You are Oxi, a helpful virtual assistant.
You can answer general knowledge questions, but for time, math, or weather you must use the provided tools.
Keep responses concise and friendly.
If a user asks about the weather, ask which city if they did not specify one.
"""

Step 3: Define the tools

Oxlo.ai supports OpenAI-compatible function definitions. We define three tools the model can request: get_current_time, calculate, and get_weather.

import json

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_current_time",
            "description": "Returns the current local date and time.",
            "parameters": {"type": "object", "properties": {}, "required": []},
        },
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Evaluates a mathematical expression safely.",
            "parameters": {
                "type": "object",
                "properties": {
                    "expression": {
                        "type": "string",
                        "description": "A math expression like '144 * 3'.",
                    }
                },
                "required": ["expression"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Returns simulated current weather for a given city.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {"type": "string", "description": "City name, e.g. Tokyo."}
                },
                "required": ["city"],
            },
        },
    },
]

Step 4: Build the agent class with memory

An assistant is useless if it forgets the user's name in the next message. We keep a message list and append each turn so context persists across requests.

class VirtualAssistant:
    def __init__(self, api_key):
        self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
        self.messages = [{"role": "system", "content": SYSTEM_PROMPT}]

    def ask(self, user_text):
        self.messages.append({"role": "user", "content": user_text})
        response = self.client.chat.completions.create(
            model="llama-3.3-70b",
            messages=self.messages,
            tools=tools,
            tool_choice="auto",
        )
        return response.choices[0].message

Step 5: Handle tool calls and loop back

When the model requests a tool, we execute the matching Python function locally, append the result as a tool message, and call Oxlo.ai again so the model can generate the final user-facing answer.

import datetime
import random

def get_current_time():
    return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

def calculate(expression):
    try:
        allowed = {"__builtins__": {}}
        return str(eval(expression, allowed, {}))
    except Exception as e:
        return f"Error: {e}"

def get_weather(city):
    conditions = ["sunny", "cloudy", "rainy", "windy"]
    temp = random.randint(15, 30)
    return f"{city} is currently {random.choice(conditions)} with a temperature of {temp}°C."

TOOL_MAP = {
    "get_current_time": get_current_time,
    "calculate": calculate,
    "get_weather": get_weather,
}

class VirtualAssistant:
    def __init__(self, api_key):
        self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key=api_key)
        self.messages = [{"role": "system", "content": SYSTEM_PROMPT}]

    def ask(self, user_text):
        self.messages.append({"role": "user", "content": user_text})

        response = self.client.chat.completions.create(
            model="llama-3.3-70b",
            messages=self.messages,
            tools=tools,
            tool_choice="auto",
        )
        msg = response.choices[0].message

        if msg.tool_calls:
            tool_calls = []
            for tc in msg.tool_calls:
                tool_calls.append({
                    "id": tc.id,
                    "type": tc.type,
                    "function": {
                        "name": tc.function.name,
                        "arguments": tc.function.arguments,
                    },
                })
            self.messages.append({
                "role": msg.role,
                "content": msg.content or "",
                "tool_calls": tool_calls,
            })

            for tc in msg.tool_calls:
                fn = tc.function.name
                args = json.loads(tc.function.arguments)
                result = TOOL_MAP[fn](**args)
                self.messages.append({
                    "role": "tool",
                    "tool_call_id": tc.id,
                    "name": fn,
                    "content": str(result),
                })

            response = self.client.chat.completions.create(
                model="llama-3.3-70b",
                messages=self.messages,
                tools=tools,
                tool_choice="auto",
            )
            msg = response.choices[0].message

        self.messages.append({"role": "assistant", "content": msg.content})
        return msg.content

Run it

Save the file as assistant.py, replace YOUR_OXLO_API_KEY, and run it. Here is a short interaction.

if __name__ == "__main__":
    assistant = VirtualAssistant(api_key="YOUR_OXLO_API_KEY")

    print("User: What is 144 times 3?")
    print("Oxi:", assistant.ask("What is 144 times 3?"))

    print("\nUser: What time is it?")
    print("Oxi:", assistant.ask("What time is it?"))

    print("\nUser: Will I need an umbrella in London?")
    print("Oxi:", assistant.ask("Will I need an umbrella in London?"))

Example output:

Oxi: 144 times 3 is 432.

Oxi: The current local time is 2026-01-15 09:42:18.

Oxi: London is currently rainy with a temperature of 19°C, so yes, bring an umbrella.

Next steps

Swap llama-3.3-70b for qwen-3-32b or kimi-k2.6 if you want stronger multilingual or agentic reasoning. You can also replace the simulated get_weather function with a real HTTP call to an open weather API and add memory storage with SQLite so conversations survive restarts.

Top comments (0)