Refactor a Chatbot into Clean Object-Oriented Components
OpenAISoftware Engineer · Technical ScreenLast updated
Tests object-oriented design and refactoring skills by decoupling a monolithic chatbot script into modular, testable, and extensible components adhering to SOLID principles.
Refactor a Monolithic Chatbot
You are handed a prototype chatbot implementation that has grown organically into a single monolithic class. The current implementation mixes conversation memory management, prompt formatting, context window pruning, tool calling, and mock model invocation in a single method.
Your task is to refactor this architecture into clean, modular, object-oriented components following SOLID design principles.
Legacy Code Issues
The legacy system has the following liabilities:
- Tightly coupled to a single hardcoded mock LLM API.
- Hardcoded string matching for tools instead of a dynamic registry.
- Memory truncation is mixed directly into the API request generation.
- No clean way to inject guards/moderation or swap out storage.
Requirements
Design and implement a clean component hierarchy with the following responsibilities:
-
Message & Memory/Context Management:
- Model messages as structured entities (
role,content, optionaltool_calls/tool_call_id). - Implement a conversation memory store that retains system instructions while truncating older dialogue when a message budget (e.g.,
max_messages) is exceeded.
- Model messages as structured entities (
-
Tool / Plugin Abstraction:
- Define a base interface for tools with a name, description, and an
execute(**kwargs) -> strmethod. - Implement a
ToolRegistrythat allows dynamic registration and execution of tools based on LLM output.
- Define a base interface for tools with a name, description, and an
-
LLM Client Interface:
- Define an abstract interface
LLMClientwith a methodgenerate(messages: list[Message], tools: list[dict]) -> LLMResponse. - Implement a
MockLLMClientfor testing.
- Define an abstract interface
-
Orchestrator (
ChatEngine):- Coordinates interaction between user input, memory, tool invocation, and the LLM client.
- Handles tool-call loops: if the model returns a tool call, execute it, append the tool response to memory, and query the model again for the final response.
Class Interface Example
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
class Message:
def __init__(self, role: str, content: str, tool_call: Optional[Dict[str, Any]] = None):
...
class Tool(ABC):
name: str
description: str
@abstractmethod
def execute(self, **kwargs) -> str:
...
class ConversationMemory:
def __init__(self, max_messages: int = 10):
...
def add_message(self, message: Message) -> None:
...
def get_context(self) -> List[Message]:
...
class ChatEngine:
def __init__(self, llm_client: "LLMClient", memory: ConversationMemory, tools: Optional["ToolRegistry"] = None):
...
def process_user_input(self, text: str) -> str:
...
Edge Cases to Consider
- Preservation of the
systemmessage regardless of how aggressively dialogue is truncated. - Handling unknown tool names gracefully without crashing the loop.
- Preventing infinite tool-execution loops (e.g., enforce a
max_tool_iterationslimit).
Hints
Hint 1
Separate stateful components (like conversation history) from stateless functional components (like tool execution and API clients).
Hint 2
Ensure the system prompt is treated as invariant during memory truncation while older user/assistant message pairs are pruned first.
Hint 3
Use dependency injection in the main orchestrator so that different LLM providers or storage strategies can be supplied without changing the engine logic.
Follow-up questions
- How would you adapt your design to support streaming responses and progressive tool execution?
- How would you integrate a token-based context window manager rather than a simple message-count cutoff?
- How would you support concurrent conversations across multiple users while maintaining thread safety?
Solution walkthrough is for members
Get the full walkthrough with reference code and complexity analysis, plus Kitty Coach at 300 messages a day. Hints, the code runner and community answers stay free.