Most teams do not greenfield their support stack. They already have a rule-based chatbot that matches intents to static replies, and they need a drop-in layer that handles edge cases with natural language. In this tutorial, we will wire an Oxlo.ai LLM into a legacy e-commerce support bot so it can answer complex, multi-turn questions while keeping the fast path for common requests.
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: Stub the legacy bot
We will start with a tiny intent classifier and a dictionary of canned responses. This mirrors what most legacy bots already do.
import json
import sqlite3
from datetime import datetime
from typing import Optional
LEGACY_RESPONSES = {
"greeting": "Hello! How can I help you today?",
"hours": "We are open 9 AM to 6 PM EST, Monday through Friday.",
"return_policy": "You can return items within 30 days with a receipt.",
}
def legacy_intent_classifier(user_msg: str) -> Optional[str]:
msg = user_msg.lower()
if any(word in msg for word in ["hello", "hi", "hey"]):
return "greeting"
if any(word in msg for word in ["hour", "open", "close"]):
return "hours"
if any(word in msg for word in ["return", "refund"]):
return "return_policy"
return None
def existing_bot_reply(user_msg: str) -> Optional[str]:
intent = legacy_intent_classifier(user_msg)
if intent:
return LEGACY_RESPONSES[intent]
return None
Step 2: Configure the Oxlo.ai client and system prompt
When the legacy layer returns None, we hand off to an LLM. Set up the Oxlo.ai client. Because Oxlo.ai is fully OpenAI-compatible, we only change the base_url and api_key.
from openai import OpenAI
client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
SYSTEM_PROMPT = """You are a concise e-commerce support assistant.
You have access to an order lookup tool. If the user asks about an order,
ask for their order ID if they have not provided it.
Keep responses under three sentences unless the user asks for detail.
Do not make up policies. If unsure, suggest contacting human support."""
Step 3: Build the LLM fallback with tool use
We will define a fake order lookup function, then teach the model to request it via Oxlo.ai's function calling support.
def get_order_status(order_id: str) -> str:
# In production, this queries your database or ERP.
fake_db = {
"ORD-001": "shipped, arriving tomorrow",
"ORD-002": "processing, expected to ship in 2 days",
}
return fake_db.get(order_id, "Order not found. Please double-check the ID.")
TOOLS = [
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Retrieve the shipping status of an order by ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order identifier, e.g. ORD-001."
}
},
"required": ["order_id"]
}
}
}
]
def llm_fallback(messages: list) -> str:
response = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = response.choices[0].message
if msg.tool_calls:
tool_call = msg.tool_calls[0]
if tool_call.function.name == "get_order_status":
args = json.loads(tool_call.function.arguments)
result = get_order_status(args["order_id"])
messages.append({
"role": "assistant",
"content": msg.content or "",
"tool_calls": [
{
"id": tool_call.id,
"type": "function",
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
}
]
})
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
second = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
)
return second.choices[0].message.content
return msg.content
Step 4: Add conversation memory
We need a small SQLite layer so the bot remembers context across turns. This lives alongside the existing logic without touching your primary database.
DB_PATH = "chat_memory.db"
def init_db():
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute("""
CREATE TABLE IF NOT EXISTS messages (
session_id TEXT,
role TEXT,
content TEXT,
timestamp TEXT
)
""")
conn.commit()
conn.close()
def load_session(session_id: str) -> list:
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute(
"SELECT role, content FROM messages WHERE session_id = ? ORDER BY timestamp",
(session_id,)
)
rows = c.fetchall()
conn.close()
return [{"role": r, "content": c} for r, c in rows]
def save_turn(session_id: str, role: str, content: str):
conn = sqlite3.connect(DB_PATH)
c = conn.cursor()
c.execute(
"INSERT INTO messages VALUES (?, ?, ?, ?)",
(session_id, role, content, datetime.utcnow().isoformat())
)
conn.commit()
conn.close()
Step 5: Wire the classifier to the LLM layer
Now we combine the legacy path and the LLM path into a single entrypoint.
def chat(session_id: str, user_msg: str) -> str:
init_db()
history = load_session(session_id)
# Fast path: legacy intents
legacy = existing_bot_reply(user_msg)
if legacy:
save_turn(session_id, "user", user_msg)
save_turn(session_id, "assistant", legacy)
return legacy
# Slow path: LLM
messages = [{"role": "system", "content": SYSTEM_PROMPT}]
messages.extend(history)
messages.append({"role": "user", "content": user_msg})
reply = llm_fallback(messages)
save_turn(session_id, "user", user_msg)
save_turn(session_id, "assistant", reply)
return reply
Run it
Save everything in bot.py and run it. The first question hits the legacy classifier, the second triggers the LLM and tool lookup, and the third keeps the conversation going.
if __name__ == "__main__":
sid = "user-42"
for text in ["What are your hours?", "Where is my order ORD-001?", "Thanks!"]:
print(f"User: {text}")
print(f"Bot: {chat(sid, text)}")
print()
Expected output:
User: What are your hours?
Bot: We are open 9 AM to 6 PM EST, Monday through Friday.
User: Where is my order ORD-001?
Bot: Your order ORD-001 has shipped and is arriving tomorrow.
User: Thanks!
Bot: You're welcome! Let me know if you need anything else.
Wrap up
Two concrete next steps. First, replace the fake get_order_status function with a real API call to your order management system. Second, if you expand conversation history or add agentic loops, consider Oxlo.ai's request-based pricing. Because cost is flat per request rather than scaling with token count, long context sessions do not inflate your bill. You can view plans at https://oxlo.ai/pricing.
Top comments (0)