Implementation Patterns¶
This document outlines common implementation patterns used in The DETERMINATOR.
Search Tools¶
All tools implement SearchTool protocol (src/tools/base.py):
- Must have
nameproperty - Must implement
async def search(query, max_results) -> list[Evidence] - Use
@retrydecorator from tenacity for resilience - Rate limiting: Implement
_rate_limit()for APIs with limits (e.g., PubMed) - Error handling: Raise
SearchErrororRateLimitErroron failures
Example pattern:
class MySearchTool:
@property
def name(self) -> str:
return "mytool"
@retry(stop=stop_after_attempt(3), wait=wait_exponential(...))
async def search(self, query: str, max_results: int = 10) -> list[Evidence]:
# Implementation
return evidence_list
Judge Handlers¶
- Implement
JudgeHandlerProtocol(async def assess(question, evidence) -> JudgeAssessment) - Use pydantic-ai
Agentwithoutput_type=JudgeAssessment - System prompts in
src/prompts/judge.py - Support fallback handlers:
MockJudgeHandler,HFInferenceJudgeHandler - Always return valid
JudgeAssessment(never raise exceptions)
Agent Factory Pattern¶
- Use factory functions for creating agents (
src/agent_factory/) - Lazy initialization for optional dependencies (e.g., embeddings, Modal)
- Check requirements before initialization:
State Management¶
- Magentic Mode: Use
ContextVarfor thread-safe state (src/agents/state.py) - Simple Mode: Pass state via function parameters
- Never use global mutable state (except singletons via
@lru_cache)
Singleton Pattern¶
Use @lru_cache(maxsize=1) for singletons:
- Lazy initialization to avoid requiring dependencies at import time
See Also¶
- Code Style - Code style guidelines
- Error Handling - Error handling guidelines