DeepTutor Best Practices: Optimizing the Two‑Layer Plugin Architecture
DeepTutor's architecture separates single‑function tools from multi‑step capabilities, requiring developers to minimize enabled tool sets, persist session IDs, and handle asynchronous streaming events for production deployments.
The HKUDS/DeepTutor repository implements a modular tutoring system built on a strict two‑layer plugin architecture that distinguishes between lightweight utilities and complex agent pipelines. Mastering these DeepTutor best practices ensures you leverage the ChatOrchestrator routing system and UnifiedContext data contract efficiently while minimizing token usage and latency. The platform streams all intermediate stages through an async StreamBus, making architectural awareness critical for responsive integrations.
Understanding the Core Two‑Layer Architecture
DeepTutor organizes functionality into distinct layers managed by dedicated registries. This separation dictates how you should structure requests and extensions.
The Entry Point and Data Flow
All interactions enter through ChatOrchestrator in deeptutor/runtime/orchestrator.py. This class routes incoming requests by examining context.active_capability (defaulting to chat) and retrieving the matching pipeline from CapabilityRegistry.
The execution flow follows this strict sequence:
- Input is wrapped in a
UnifiedContext(defined indeeptutor/core/context.py) containing session metadata, enabled tools, knowledge bases, and configuration overrides. ChatOrchestrator.handle()dispatches to the selected capability, instantiating a freshStreamBusfromdeeptutor/core/stream_bus.py.- The capability invokes specific tools via
ToolRegistry(deeptutor/runtime/registry/tool_registry.py), which provides OpenAI‑compatible schemas for each utility. - Tools return
ToolResultobjects, while the capability streams progress throughStreamEventinstances. - Upon completion, the orchestrator emits a
CAPABILITY_COMPLETEevent on the globalEventBus.
Tools vs. Capabilities
Understanding the granularity difference prevents architectural misuse:
- Tools (Level 1): Single‑function utilities registered in
ToolRegistry. Examples includerag,code_execution, orweb_search. These perform isolated, stateless operations. - Capabilities (Level 2): Multi‑step agent pipelines registered in
CapabilityRegistry(deeptutor/runtime/registry/capability_registry.py). These orchestrate sequences of tool calls to accomplish complex workflows likedeep_solveordeep_question.
Use tools for lightweight, on‑demand actions. Use capabilities for higher‑level reasoning workflows that require coordinated tool invocation.
Optimizing Tool Selection and Configuration
Efficient DeepTutor usage requires strict control over which components load for each request.
Minimize Enabled Tools
The orchestrator loads only tools specified in context.enabled_tools (or via the CLI -t flag). Loading unnecessary tools increases latency and token consumption.
# Efficient: Load only RAG for a knowledge retrieval task
deeptutor run chat "Explain quantum entanglement" -t rag --kb physics-kb
# Inefficient: Loading reasoning tools when not required
deeptutor run chat "Explain quantum entanglement" -t rag,reason,code_execution --kb physics-kb
Leverage Knowledge Bases and Session Persistence
Attach knowledge bases (--kb <name>) to every turn requiring domain‑specific information. For multi‑turn interactions, ensure the session_id in UnifiedContext persists across requests so the memory module can accumulate a coherent learner profile.
In the interactive REPL (deeptutor chat), the system handles session continuity automatically:
deeptutor chat
/cap deep_solve # Switch to multi-step reasoning capability
/tool rag,reason # Enable specific tools for this session
/kb my-kb # Set active knowledge base
/config temperature=0.7 # Override model parameters for current turn
Use Configuration Overrides
Fine‑tune behavior per request using context.config_overrides or the --config CLI argument. This allows dynamic adjustment of temperature, max tokens, or system prompts without modifying global settings.
Implementation Patterns for Different Interfaces
DeepTutor exposes three primary interfaces, each suited to specific operational contexts.
CLI One‑Shot Execution
Use for atomic tasks where session continuity is unnecessary. The deeptutor run command (entry point in deeptutor_cli/main.py) accepts capability names, tool lists, and configuration overrides as arguments.
# Deep problem solving with specific reasoning tools
deeptutor run deep_solve "Prove the fundamental theorem of calculus" -t reason
# Quiz generation with controlled output
deeptutor run deep_question "Linear algebra basics" --config num_questions=5 --kb math-kb
Interactive REPL Sessions
The REPL maintains persistent UnifiedContext and session_id across commands, making it ideal for iterative tutoring scenarios. Switch capabilities and toolsets mid‑session using slash commands.
Python SDK Integration
For embedding within larger agent systems, instantiate ChatOrchestrator directly and consume the async generator returned by handle(). This is the pattern used by the WebSocket endpoint in deeptutor/api/routers/unified_ws.py.
from deeptutor.runtime.orchestrator import ChatOrchestrator
from deeptutor.core.context import UnifiedContext
async def tutoring_query(question: str):
ctx = UnifiedContext(
user_message=question,
enabled_tools=["rag", "code_execution"],
knowledge_bases=["my-kb"],
language="en",
metadata={"origin": "auto_agent"} # Trace request provenance
)
orchestrator = ChatOrchestrator()
async for event in orchestrator.handle(ctx):
if event.type == "content":
print(event.payload) # Stream partial results
elif event.type == "result":
print("Final:", event.payload) # Structured completion
Handle content events for responsive UI feedback; reserve result events for final structured payloads.
Extending DeepTutor with Custom Plugins
The system auto‑discovers extensions via deeptutor/plugins/loader.py. Create custom tools by implementing the BaseTool protocol from deeptutor/core/tool_protocol.py.
# custom_tools/math_tool.py
from deeptutor.core.tool_protocol import BaseTool, ToolResult, ToolDefinition
class SquareTool(BaseTool):
name = "square"
description = "Returns the square of a number"
async def execute(self, number: int) -> ToolResult:
return ToolResult(output=str(number ** 2))
def get_definition(self) -> ToolDefinition:
return ToolDefinition(
name=self.name,
description=self.description,
parameters={
"type": "object",
"properties": {"number": {"type": "integer"}},
"required": ["number"]
}
)
Register at startup:
from deeptutor.runtime.registry.tool_registry import get_tool_registry
from custom_tools.math_tool import SquareTool
get_tool_registry().register(SquareTool())
Critical requirements: Keep tool side effects pure and outputs JSON‑serializable to ensure safe transmission through StreamBus.
Summary
- Align granularity with needs: Use capabilities for complex workflows, tools for single operations.
- Minimize tool loading: Specify only required tools via
-torenabled_toolsto reduce latency. - Persist sessions: Maintain consistent
session_idvalues across related interactions for coherent memory profiles. - Stream events properly: Handle
contentevents for UX responsiveness andresultevents for final data. - Attach knowledge bases: Always include
--kbwhen domain context is required. - Extend via plugins: Implement
BaseToolfor custom utilities; the loader discovers them automatically from the plugins directory.
Frequently Asked Questions
What is the difference between a tool and a capability in DeepTutor?
A tool is a single‑function utility (like rag or code_execution) registered in ToolRegistry that performs an isolated task and returns a ToolResult. A capability is a multi‑step pipeline (like deep_solve) registered in CapabilityRegistry that orchestrates multiple tool calls to complete complex objectives. Tools are the building blocks; capabilities are the workflows.
How do I maintain context across multiple user interactions?
Persist the session_id field within UnifiedContext across sequential calls to ChatOrchestrator.handle(). The REPL interface handles this automatically, but when using the Python SDK, you must manually reuse the same session identifier to allow the memory module to build a continuous learner profile.
Why should I limit the number of enabled tools per request?
Each enabled tool consumes tokens for its schema description and adds initialization overhead. The orchestrator in runtime/orchestrator.py only loads tools specified in enabled_tools, so keeping this list minimal reduces both latency and API costs while preventing context window saturation.
How do I add custom functionality to DeepTutor?
Create a class inheriting from BaseTool in deeptutor/core/tool_protocol.py, implement execute() and get_definition(), and register it with ToolRegistry at startup. For complex workflows, package capabilities with a manifest.yaml file; the plugins/loader.py module auto‑discovers these extensions at runtime.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →