DEV Community

shashank ms
shashank ms

Posted on

Building Virtual Assistants with LLM: A Step-by-Step Guide

I needed a lightweight customer support bot that could answer FAQ questions, look up mock account data, and reset passwords without spinning up a complex framework. I built it in pure Python on top of Oxlo.ai's flat per-request API, which keeps costs predictable even when conversation history grows. Here is exactly how I wired it together.

What you'll need

Step 1: Bootstrap the Oxlo.ai client

I start by importing the SDK and pointing the client at Oxlo.ai. I picked llama-3.3-70b because it is the general-purpose flagship and handles tool use reliably. A one-line test confirms the endpoint is responding.

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 support assistant."},
        {"role": "user", "content": "Are you online?"},
    ],
)

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

Step 2: Craft the system prompt

The system prompt is the only training the bot gets. I keep it strict: define the persona, enumerate the three available tools, and instruct the model to ask for a user_id before calling account-related functions.

SYSTEM_PROMPT = """You are SupportBot, a customer support assistant for Acme SaaS.
Your job is to answer billing and account questions.
You have access to the following tools:
- search_faq(query): Search the help center.
- check_account_status(user_id): Return plan and payment status.
- reset_password(user_id): Trigger a password reset email.
Only call tools when needed. Ask for the user_id if it is missing."""

Step 3: Add conversation memory

To make this a virtual assistant rather than a stateless endpoint, I maintain a messages list. Every turn appends the user question and assistant reply, so context survives across questions.

messages = [
    {"role": "system", "content": SYSTEM_PROMPT},
]

def chat(user_input: str) -> str:
    messages.append({"role": "user", "content": user_input})
    response = client.chat.completions.create(
        model="llama-3.3-70b",
        messages=messages,
    )
    reply = response.choices[0].message.content
    messages.append({"role": "assistant", "content": reply})
    return reply

Step 4: Wire up tools and handlers

Now I implement the actual tools as plain Python functions and define the JSON schemas the model needs to invoke them. The chat loop detects tool calls, executes the functions, feeds the results back into the conversation, and repeats until the model produces a final answer.

import json

def search_faq(query: str) -> str:
    db = {
        "refund": "Refunds are processed within 5 business days.",
        "upgrade": "You can upgrade from the Billing page.",
        "cancel": "Cancellation takes effect at the end of the billing cycle.",
    }
    return db.get(query.lower(), "No exact match found.")

def check_account_status(user_id: str) -> str:
    return json.dumps({"user_id": user_id, "plan": "Pro", "status": "active", "due": 0})

def reset_password(user_id: str) -> str:
    return json.dumps({"user_id": user_id, "status": "reset_email_sent"})

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "search_faq",
            "description": "Search the help center for a topic.",
            "parameters": {
                "type": "object",
                "properties": {
                    "query": {"type": "string", "description": "The search keyword."}
                },
                "required": ["query"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "check_account_status",
            "description": "Check a user's account status.",
            "parameters": {
                "type": "object",
                "properties": {
                    "user_id": {"type": "string", "description": "The user ID."}
                },
                "required": ["user_id"],
            },
        },
    },
    {
        "type": "function",
        "function": {
            "name": "reset_password",
            "description": "Send a password reset email.",
            "parameters": {
                "type": "object",
                "properties": {
                    "user_id": {"type": "string", "description": "The user ID."}
                },
                "required": ["user_id"],
            },
        },
    },
]

def handle_tool_calls(message):
    tool_call_payload = []
    for tc in message.tool_calls:
        tool_call_payload.append({
            "id": tc.id,
            "type": tc.type,
            "function": {
                "name": tc.function.name,
                "arguments": tc.function.arguments,
            },
        })
    messages.append({
        "role": "assistant",
        "content": message.content or "",
        "tool_calls": tool_call_payload,
    })

    for tc in message.tool_calls:
        fn_name = tc.function.name
        args = json.loads(tc.function.arguments)

        if fn_name == "search_faq":
            result = search_faq(args["query"])
        elif fn_name == "check_account_status":
            result = check_account_status(args["user_id"])
        elif fn_name == "reset_password":
            result = reset_password(args["user_id"])
        else:
            result = "Unknown tool."

        messages.append({
            "role": "tool",
            "tool_call_id": tc.id,
            "content": str(result),
        })

def chat(user_input: str) -> str:
    messages.append({"role": "user", "content": user_input})
    while True:
        response = client.chat.completions.create(
            model="llama-3.3-70b",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto",
        )
        message = response.choices[0].message

        if message.tool_calls:
            handle_tool_calls(message)
        else:
            messages.append({"role": "assistant", "content": message.content})
            return message.content

Run it

Save the complete script as bot.py, export your key, and run it. The transcript below shows the assistant searching the FAQ, asking for a missing user ID, checking an account, and triggering a password reset.

$ export OXLO_API_KEY="sk-..."
$ python bot.py

User: How do I upgrade my plan?
SupportBot: You can upgrade from the Billing page.

User: Can you check my account?
SupportBot: Sure, what is your user ID?
User: u-12345
SupportBot: Your Pro account is active with no outstanding balance.

User: I forgot my password.
SupportBot: A password reset email has been sent to u-12345.

Next steps

To productionize this, add streaming responses so users see text as it generates, or wrap the loop in a FastAPI endpoint. If you need deeper reasoning for tricky troubleshooting flows, swap the model to deepseek-v3.2 or kimi-k2.6 without changing any other logic. Because Oxlo.ai uses flat per-request pricing, you can keep the full conversation history in context without the cost scaling you would see on token-based providers. See https://oxlo.ai/pricing for plan details.

Top comments (0)