We are building a conversational order-support agent that answers policy questions, looks up shipments, and initiates returns. It is meant for any team that wants to automate tier-1 support without surprise costs as conversation history grows. Because Oxlo.ai charges per request rather than per token, long system prompts and multi-turn threads stay predictable.
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: Verify the client
First, confirm that your environment can reach Oxlo.ai and that the API key is active. A single chat completion is enough to prove the path works.
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: System prompt and knowledge base
Next, give the model its identity and a small in-memory knowledge base. In production you would swap this for a vector store, but a static dictionary keeps the tutorial self-contained.
SYSTEM_PROMPT = """You are an order support agent for Acme Devices.
Use the knowledge base to answer general questions.
If the user asks about an order or wants a return, call the correct tool.
Knowledge base:
- Domestic shipping takes 3-5 business days. International takes 7-10.
- Returns are accepted within 30 days of delivery.
- All electronics include a 1-year limited warranty.
"""
ORDERS_DB = {
"ORD-1001": {"status": "shipped", "item": "Noise-Canceling Headphones", "delivered": False},
"ORD-1002": {"status": "delivered", "item": "Mechanical Keyboard", "delivered": True},
}
Step 3: Define tools
Now we declare the two actions the agent can take: checking an order and starting a return. The schema follows the OpenAI functions format, which Oxlo.ai supports natively.
import json
TOOLS = [
{
"type": "function",
"function": {
"name": "check_order_status",
"description": "Get the current status of an order by ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID, e.g. ORD-1001"}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "start_return",
"description": "Initiate a return for a delivered order.",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The delivered order ID"}
},
"required": ["order_id"]
}
}
}
]
def check_order_status(order_id: str):
order = ORDERS_DB.get(order_id)
if not order:
return {"error": "Order not found"}
return {"order_id": order_id, **order}
def start_return(order_id: str):
order = ORDERS_DB.get(order_id)
if not order:
return {"error": "Order not found"}
if not order["delivered"]:
return {"error": "Cannot return an order that has not been delivered"}
return {"status": "return_started", "order_id": order_id, "label": f"https://return.acme.test/R-{order_id}"}
Step 4: Single-turn tool loop
With the schema ready, we write a function that sends the user message to the model, executes any requested tools, and sends the results back for a final answer.
def run_turn(user_message: str) -> str:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_message},
]
resp = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = resp.choices[0].message
if msg.tool_calls:
# Record the model's request to call tools
messages.append({
"role": "assistant",
"content": msg.content or "",
"tool_calls": [
{
"id": tc.id,
"type": "function",
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in msg.tool_calls
],
})
for tc in msg.tool_calls:
fn_name = tc.function.name
args = json.loads(tc.function.arguments)
if fn_name == "check_order_status":
result = check_order_status(**args)
elif fn_name == "start_return":
result = start_return(**args)
else:
result = {"error": "Unknown function"}
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
# Get the final natural-language answer
final = client.chat.completions.create(
model="llama-3.3-70b",
messages=messages,
)
return final.choices[0].message.content
return msg.content
print(run_turn("Where is my order ORD-1001?"))
Step 5: Multi-turn memory
Finally, we wrap the loop in a small class so the message history persists across turns. This lets the agent handle follow-up questions like "Can I return it?" without repeating context.
class SupportAgent:
def __init__(self):
self.client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")
self.messages = [{"role": "system", "content": SYSTEM_PROMPT}]
def chat(self, user_message: str) -> str:
self.messages.append({"role": "user", "content": user_message})
resp = self.client.chat.completions.create(
model="llama-3.3-70b",
messages=self.messages,
tools=TOOLS,
tool_choice="auto",
)
msg = resp.choices[0].message
if msg.tool_calls:
self.messages.append({
"role": "assistant",
"content": msg.content or "",
"tool_calls": [
{
"id": tc.id,
"type": "function",
"function": {
"name": tc.function.name,
"arguments": tc.function.arguments,
},
}
for tc in msg.tool_calls
],
})
for tc in msg.tool_calls:
fn_name = tc.function.name
args = json.loads(tc.function.arguments)
if fn_name == "check_order_status":
result = check_order_status(**args)
elif fn_name == "start_return":
result = start_return(**args)
else:
result = {"error": "Unknown function"}
self.messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result),
})
final = self.client.chat.completions.create(
model="llama-3.3-70b",
messages=self.messages,
)
reply = final.choices[0].message.content
else:
reply = msg.content
self.messages.append({"role": "assistant", "content": reply})
return reply
Run it
The snippet below creates the agent and runs two back-to-back turns. The first turn checks an order, and the second initiates a return.
agent = SupportAgent()
print("User: Where is my order ORD-1002?")
print("Agent:", agent.chat("Where is my order ORD-1002?"))
print("\nUser: I need to return it. The spacebar is sticky.")
print("Agent:", agent.chat("I need to return it. The spacebar is sticky."))
Expected output:
User: Where is my order ORD-1002?
Agent: Your order ORD-1002 for Mechanical Keyboard has been delivered.
User: I need to return it. The spacebar is sticky.
Agent: I've started your return for order ORD-1002. You can print your label here: https://return.acme.test/R-ORD-1002
Wrap-up
Two concrete next steps. First, replace the static ORDERS_DB with a real SQL or API lookup. Second, move the knowledge base into a vector store and use Oxlo.ai's embeddings endpoint to retrieve only the relevant articles for each query. Because Oxlo.ai bills per request rather than per token, adding retrieval context or extending the chat history does not inflate cost the way token-based providers do. See https://oxlo.ai/pricing for details.
Top comments (0)