DEV Community

shashank ms
shashank ms

Posted on

Chain-of-Thought Reasoning Model Explained

I have been using chain-of-thought prompting to debug why language models fail on simple logic problems. The technique works because it forces the model to surface its reasoning before locking in an answer. In this tutorial, I will build a small Python agent that uses Oxlo.ai to generate explicit reasoning traces, then splits them from the final answer so you can log and audit both.

What you'll need

  • Python 3.10 or newer
  • The OpenAI SDK: pip install openai
  • An Oxlo.ai API key from https://portal.oxlo.ai. Oxlo.ai offers a free tier that includes deepseek-v3.2, so you can run this tutorial without a credit card.

Step 1: Configure the OpenAI client for Oxlo.ai

First, instantiate the OpenAI client pointing at Oxlo.ai. The base URL and SDK are fully compatible, so the only difference from OpenAI is the endpoint and the model name.

from openai import OpenAI

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

MODEL = "deepseek-v3.2"

Step 2: Write the chain-of-thought system prompt

The system prompt is the only training the agent gets. I force the model to emit reasoning inside <thinking> tags and the final answer inside <answer> tags so parsing is trivial and the model shows its work.

SYSTEM_PROMPT = """You are a reasoning assistant that solves problems step by step.

Rules:
1. Think through the problem inside <thinking> tags. Show arithmetic, logic, and intermediate conclusions.
2. Place the final, concise answer inside <answer> tags.
3. Do not output any text outside these two tags.

Example:
<thinking>
The user asks: 15 + 27.
15 + 20 = 35.
35 + 7 = 42.
</thinking>
<answer>
42
</answer>
"""

Step 3: Send a raw request

Before wrapping everything in classes, I verify that the model follows the format. I send a simple word problem and inspect the raw content.

problem = "A farmer has 10 cows. All but 3 die. How many cows are left?"

response = client.chat.completions.create(
    model=MODEL,
    messages=[
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": problem},
    ],
    temperature=0.2,
)

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

Step 4: Parse the reasoning trace

The raw string contains both parts. I extract them with regular expressions and fall back to treating the entire output as reasoning if the tags are missing.

import re

def parse_cot(text: str) -> dict:
    thinking_match = re.search(r"<thinking>(.*?)</thinking>", text, re.DOTALL)
    answer_match = re.search(r"<answer>(.*?)</answer>", text, re.DOTALL)
    
    return {
        "reasoning": thinking_match.group(1).strip() if thinking_match else text,
        "answer": answer_match.group(1).strip() if answer_match else "NO_ANSWER_FOUND",
    }

parsed = parse_cot(raw_output)
print("Reasoning:", parsed["reasoning"])
print("Answer:", parsed["answer"])

Step 5: Wrap it in a reusable agent

Now I package the client, parser, and prompt into a single class. This hides the plumbing and lets me swap in different Oxlo.ai models, such as kimi-k2.6 for harder reasoning or qwen-3-32b for multilingual problems, without changing the interface.

class ChainOfThoughtAgent:
    def __init__(self, api_key: str, model: str = "deepseek-v3.2"):
        self.client = OpenAI(
            base_url="https://api.oxlo.ai/v1",
            api_key=api_key,
        )
        self.model = model
        self.system_prompt = SYSTEM_PROMPT

    def solve(self, problem: str, temperature: float = 0.2) -> dict:
        resp = self.client.chat.completions.create(
            model=self.model,
            messages=[
                {"role": "system", "content": self.system_prompt},
                {"role": "user", "content": problem},
            ],
            temperature=temperature,
        )
        raw = resp.choices[0].message.content
        return self._parse(raw)

    @staticmethod
    def _parse(text: str) -> dict:
        t = re.search(r"<thinking>(.*?)</thinking>", text, re.DOTALL)
        a = re.search(r"<answer>(.*?)</answer>", text, re.DOTALL)
        return {
            "reasoning": t.group(1).strip() if t else text,
            "answer": a.group(1).strip() if a else "NO_ANSWER_FOUND",
        }

Run it

Here is the full script that exercises the agent on two problems. Because Oxlo.ai uses flat per-request pricing, I can stuff the system prompt with long few-shot examples and still know exactly what each call costs.

if __name__ == "__main__":
    agent = ChainOfThoughtAgent(api_key="YOUR_OXLO_API_KEY")
    
    problems = [
        "Roger has 5 tennis balls. He buys 2 more cans of tennis balls. Each can has 3 balls. How many does he have now?",
        "If it takes 5 machines 5 minutes to make 5 widgets, how long does it take 100 machines to make 100 widgets?",
    ]
    
    for p in problems:
        result = agent.solve(p)
        print(f"Problem: {p}")
        print(f"Reasoning: {result['reasoning']}")
        print(f"Answer: {result['answer']}")
        print("-" * 40)

Example output:

Problem: Roger has 5 tennis balls. He buys 2 more cans of tennis balls. Each can has 3 balls. How many does he have now?
Reasoning: Roger starts with 5 balls. He buys 2 cans, each with 3 balls, so 2 * 3 = 6 new balls. 5 + 6 = 11 total balls.
Answer: 11
----------------------------------------
Problem: If it takes 5 machines 5 minutes to make 5 widgets, how long does it take 100 machines to make 100 widgets?
Reasoning: 5 machines make 5 widgets in 5 minutes, so each machine makes 1 widget in 5 minutes. 100 machines make 100 widgets in the same 5 minutes because they work in parallel.
Answer: 5 minutes
----------------------------------------

Next steps

Swap in kimi-k2.6 or qwen-3-32b to compare reasoning styles across models on Oxlo.ai. You can also stream the reasoning in real time by passing stream=True to client.chat.completions.create and yielding partial chunks to a UI.

If you want to productionize this, add Pydantic validation to the parser and log every reasoning trace to a database. That audit trail is the main reason to use chain-of-thought in the first place.

Top comments (0)