DEV Community

shashank ms
shashank ms

Posted on

Integrating LLM with Existing Chatbot

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

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)