Phase 04: Single Model Q&A - 2 plans created - 5 total tasks defined - Ready for execution Plans: - 04-01: AI client abstraction (openai dep, config, AIClient class) - 04-02: /ask handler and bot integration (M3 milestone) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
5 KiB
| phase | plan | type |
|---|---|---|
| 04-single-model-qa | 01 | execute |
Purpose: Establish the foundation for all AI model interactions - single queries, multi-model discussions, and consensus generation all flow through this client. Output: Working ai_client.py that can send prompts to any model via Requesty or OpenRouter.
<execution_context> ~/.claude/get-shit-done/workflows/execute-phase.md ~/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-project-crud/03-03-SUMMARY.mdKey files:
@src/moai/bot/config.py @src/moai/core/models.py
From discovery (no DISCOVERY.md needed - Level 1):
Both Requesty and OpenRouter are OpenAI SDK compatible:
- Requesty: base_url="https://router.requesty.ai/v1", model format "provider/model-name"
- OpenRouter: base_url="https://openrouter.ai/api/v1", needs HTTP-Referer header
Can use openai package with different base_url/headers
Tech available:
- python-telegram-bot, sqlalchemy, httpx, aiosqlite
- pytest, pytest-asyncio
Established patterns:
- Service layer in core/services/
- Config loading from environment in bot/config.py
- Async functions throughout
Constraining decisions:
- AI client as abstraction layer (PROJECT.md)
- httpx for API calls (SPEC.md)
Note: Use existing pattern of loading from env with os.getenv(). No need for pydantic or complex validation - keep it simple like existing Config class. python -c "from moai.bot.config import Config; c = Config(); print(c.AI_ROUTER)" Config has AI_ROUTER, AI_API_KEY, AI_REFERER attributes; openai in dependencies
Task 2: Create AI client abstraction src/moai/core/ai_client.py Create ai_client.py with:-
AIClient class that wraps OpenAI AsyncOpenAI client:
class AIClient: def __init__(self, router: str, api_key: str, referer: str | None = None): # Set base_url based on router ("requesty" or "openrouter") # Store referer for OpenRouter # Create AsyncOpenAI client with base_url and api_key -
Async method for single completion:
async def complete(self, model: str, messages: list[dict], system_prompt: str | None = None) -> str: # Build messages list with optional system prompt # Call client.chat.completions.create() # Add extra_headers with HTTP-Referer if OpenRouter and referer set # Return response.choices[0].message.content -
Model name normalization:
- For Requesty: model names need provider prefix (e.g., "claude" -> "anthropic/claude-sonnet-4-20250514")
- For OpenRouter: similar format
- Create MODEL_MAP dict with our short names -> full model identifiers
- MODEL_MAP = {"claude": "anthropic/claude-sonnet-4-20250514", "gpt": "openai/gpt-4o", "gemini": "google/gemini-2.0-flash"}
-
Module-level convenience function:
_client: AIClient | None = None def init_ai_client(config: Config) -> AIClient: global _client _client = AIClient(config.AI_ROUTER, config.AI_API_KEY, config.AI_REFERER) return _client def get_ai_client() -> AIClient: if _client is None: raise RuntimeError("AI client not initialized") return _client
Keep it minimal - no retry logic, no streaming (yet), no complex error handling. This is the foundation; complexity comes later as needed. python -c "from moai.core.ai_client import AIClient, MODEL_MAP; print(MODEL_MAP)" AIClient class exists with complete() method, MODEL_MAP has claude/gpt/gemini mappings
Before declaring plan complete: - [ ] `uv sync` installs openai package - [ ] Config loads AI settings from environment - [ ] AIClient can be instantiated with router/key - [ ] MODEL_MAP contains claude, gpt, gemini mappings - [ ] `ruff check src` passes<success_criteria>
- openai package in dependencies
- Config extended with AI_ROUTER, AI_API_KEY, AI_REFERER
- AIClient class with complete() method
- MODEL_MAP with short name -> full model mappings
- Module-level init_ai_client/get_ai_client functions
- All code follows project conventions (type hints, docstrings) </success_criteria>