DEV Community

shashank ms
shashank ms

Posted on

Understanding Agentic Workload for LLM

Agentic workloads are not single prompts. They are loops of reasoning, tool calls, and context accumulation that can quickly inflate costs on token-based providers. In this tutorial, I will build a research agent that answers complex questions by iterating through search and calculation steps, and I will run it on Oxlo.ai because flat per-request pricing keeps these multi-turn loops predictable.

What you'll need

Step 1: Scaffold the client and tools

I start by initializing the OpenAI-compatible client pointing at Oxlo.ai. I run a quick connectivity test with the exact calling pattern, then define two mock tools so the tutorial is self-contained.

import json
from openai import OpenAI

client = OpenAI(base_url="https://api.oxlo.ai/v1", api_key="YOUR_OXLO_API_KEY")

# Verify the connection using the standard Oxlo.ai pattern
response = client.chat.completions.create(
    model="llama-3.3-70b",
    messages=[
        {"role": "system", "content": "You are a helpful assistant."},
        {"role": "user", "content": "Say hello"},
    ],
)
print("Oxlo.ai response:", response.choices[0].message.content)

def web_search(query: str):
    """Mock search. Replace with a real search API in production."""
    data = {
        "tokyo population": "Tokyo's population is approximately 14 million (2024).",
        "gdp per capita japan": "Japan's GDP per capita is roughly $34,000 USD (2024 estimate)."
    }
    for key, val in data.items():
        if key in query.lower():
            return {"result": val}
    return {"result": f"No direct result for: {query}"}

def calculate(expression: str):
    allowed = set("0123456789+-*/.() ")
    if all(c in allowed for c in expression):
        try:
            return {"result": eval(expression, {"__builtins__": {}}, {})}
        except Exception as e:
            return {"error": str(e)}
    return {"error": "Invalid expression"}

TOOLS = [
    {
        "type": "function",
        "function": {
            "name": "web_search",
            "description": "Search the web for factual information.",
            "parameters": {
                "type": "object",
                "properties": {"query": {"type": "string"}},
                "required": ["query"]
            }
        }
    },
    {
        "type": "function",
        "function": {
            "name": "calculate",
            "description": "Evaluate a mathematical expression safely.",
            "parameters": {
                "type": "object",
                "properties": {"expression": {"type": "string"}},
                "required": ["expression"]
            }
        }
    }
]

Step 2: Define the agent's system prompt

The system prompt is the agent's contract. It tells the model how to reason, when to call tools, and when to stop.

SYSTEM_PROMPT = """You are a research agent. Answer the user's question by reasoning step by step.
You have access to two tools:
- web_search: to find factual information.
- calculate: to perform math.

Follow this process:
1. Think about what you need to know.
2. Call one tool at a time.
3. After you have enough information, provide a concise final answer.

Do not guess. Use tools when facts or math are required."""

Step 3: Build the reasoning loop

This is the core of the agentic workload. I send the conversation to Oxlo.ai, let the model decide whether to call a tool, execute the tool locally, and feed the result back into the next request. The loop continues until the model returns a plain text answer.

def run_agent(question: str, max_steps: int = 5):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question}
    ]

    for _ in range(max_steps):
        response = client.chat.completions.create(
            model="qwen-3-32b",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto"
        )

        message = response.choices[0].message
        messages.append(message)

        if not message.tool_calls:
            return message.content

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

            if name == "web_search":
                observation = web_search(**args)
            elif name == "calculate":
                observation = calculate(**args)
            else:
                observation = {"error": "Unknown tool"}

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "name": name,
                "content": json.dumps(observation)
            })

    return "Reached step limit without final answer."

Step 4: Manage growing context

Agentic runs accumulate messages quickly. On token-based providers, every extra line in the context window adds cost. Oxlo.ai charges per request, not per token, so the price stays flat even as the prompt grows. Still, I trim stale tool turns to keep latency low and the model focused.

def compact_history(messages, keep_last=6):
    """Preserve the system prompt and first user message, then keep only the most recent turns."""
    if len(messages) <= keep_last + 2:
        return messages
    header = [messages[0], messages[1]]
    tail = messages[-keep_last:]
    return header + tail

def run_agent(question: str, max_steps: int = 5):
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": question}
    ]

    for _ in range(max_steps):
        messages = compact_history(messages)

        response = client.chat.completions.create(
            model="qwen-3-32b",
            messages=messages,
            tools=TOOLS,
            tool_choice="auto"
        )

        message = response.choices[0].message
        messages.append(message)

        if not message.tool_calls:
            return message.content

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

            if name == "web_search":
                observation = web_search(**args)
            elif name == "calculate":
                observation = calculate(**args)
            else:
                observation = {"error": "Unknown tool"}

            messages.append({
                "role": "tool",
                "tool_call_id": tool_call.id,
                "name": name,
                "content": json.dumps(observation)
            })

    return "Reached step limit without final answer."

Run it

Now I can ask a multi-step question that forces the agent to search twice and then calculate.

if __name__ == "__main__":
    question = "What is the GDP per capita of Japan multiplied by the population of Tokyo?"
    print(run_agent(question))

Example output:

Japan's GDP per capita is roughly $34,000 USD, and Tokyo's population is approximately 14 million.
34000 * 14000000 = 476000000000
The result is $476 billion.

Wrap-up

You now have a working agentic loop on Oxlo.ai. To productionize it, swap the mock web_search for a real search API, and add retry logic around the Oxlo.ai client calls. If you are running dozens of agent steps per task, Oxlo.ai's request-based pricing removes the penalty for long context windows that token-based providers charge.

Top comments (0)