184 lines
5.4 KiB
Markdown
184 lines
5.4 KiB
Markdown
# Add a New Model Backend
|
|
|
|
SkillOpt supports multiple LLM backends. This guide shows how to add your own.
|
|
|
|
## Built-in: the generic OpenAI-compatible backend
|
|
|
|
Before writing a new backend, check whether your provider already speaks the
|
|
OpenAI Chat Completions protocol. Most do — in which case you can use the
|
|
built-in **`openai_compatible`** backend
|
|
(`skillopt/model/openai_compatible_backend.py`) with no code changes.
|
|
|
|
A single `base_url` + `api_key` pair lets you point SkillOpt at, for example:
|
|
|
|
| Provider | `base_url` | Example model |
|
|
|---|---|---|
|
|
| DeepSeek | `https://api.deepseek.com/v1` | `deepseek-chat` |
|
|
| Groq | `https://api.groq.com/openai/v1` | `llama-3.3-70b-versatile` |
|
|
| Together AI | `https://api.together.xyz/v1` | `meta-llama/Llama-3.3-70B-Instruct-Turbo` |
|
|
| Ollama (local) | `http://localhost:11434/v1` | `qwen2.5:7b` |
|
|
| vLLM / SGLang / TGI | `http://localhost:8000/v1` | your served model |
|
|
| LiteLLM proxy | `http://localhost:4000` | any proxied model |
|
|
| OpenRouter / Fireworks / xAI / … | provider base URL | provider model id |
|
|
|
|
Select it as the optimizer and/or target backend:
|
|
|
|
```python
|
|
import skillopt.model as model
|
|
|
|
# Shorthand: use it for both optimizer and target.
|
|
model.set_backend("openai_compatible")
|
|
|
|
# Point it at a provider (shared, or per-role with optimizer_*/target_*).
|
|
model.configure_openai_compatible(
|
|
base_url="https://api.deepseek.com/v1",
|
|
api_key="sk-...",
|
|
model="deepseek-chat",
|
|
)
|
|
```
|
|
|
|
Or configure it entirely through environment variables (role-specific
|
|
`OPTIMIZER_*` / `TARGET_*` variants override the shared ones):
|
|
|
|
```bash
|
|
export TARGET_BACKEND=openai_compatible
|
|
export OPENAI_COMPATIBLE_BASE_URL="https://api.groq.com/openai/v1"
|
|
export OPENAI_COMPATIBLE_API_KEY="gsk_..."
|
|
export OPENAI_COMPATIBLE_MODEL="llama-3.3-70b-versatile"
|
|
# Optional: OPENAI_COMPATIBLE_TEMPERATURE, _MAX_TOKENS, _TIMEOUT_SECONDS
|
|
```
|
|
|
|
The backend uses the official `openai` SDK, records token usage through the
|
|
shared tracker, supports tool/function calling via
|
|
`chat_target_messages(..., tools=...)`, and exposes
|
|
`count_tokens()` (tiktoken with a character-based fallback for non-OpenAI
|
|
models). Only write a brand-new backend if your provider is *not*
|
|
OpenAI-compatible.
|
|
|
|
## Backend Architecture
|
|
|
|
```
|
|
skillopt/model/
|
|
├── base.py # Abstract base class
|
|
├── azure_openai.py # Azure OpenAI backend
|
|
├── openai_model.py # Direct OpenAI backend
|
|
├── claude.py # Anthropic Claude backend
|
|
├── qwen.py # Local Qwen (vLLM) backend
|
|
└── your_backend.py # Your new backend
|
|
```
|
|
|
|
## Step 1: Create the Backend
|
|
|
|
Create `skillopt/model/your_backend.py`:
|
|
|
|
```python
|
|
from skillopt.model.base import ModelBackend, ModelResponse
|
|
|
|
class YourBackend(ModelBackend):
|
|
"""Your custom model backend."""
|
|
|
|
def __init__(self, cfg: dict):
|
|
super().__init__(cfg)
|
|
self.model_name = cfg.get('model_name', 'your-default-model')
|
|
self.api_key = os.environ.get('YOUR_API_KEY', '')
|
|
self.client = self._init_client()
|
|
|
|
def _init_client(self):
|
|
"""Initialize API client."""
|
|
# TODO: Set up your API client
|
|
pass
|
|
|
|
async def generate(
|
|
self,
|
|
messages: list[dict],
|
|
temperature: float = 0.7,
|
|
max_tokens: int = 4096,
|
|
**kwargs
|
|
) -> ModelResponse:
|
|
"""
|
|
Generate a completion.
|
|
|
|
Args:
|
|
messages: Chat messages [{"role": "...", "content": "..."}]
|
|
temperature: Sampling temperature
|
|
max_tokens: Maximum tokens in response
|
|
|
|
Returns:
|
|
ModelResponse with content, usage, and metadata
|
|
"""
|
|
response = await self.client.chat(
|
|
model=self.model_name,
|
|
messages=messages,
|
|
temperature=temperature,
|
|
max_tokens=max_tokens,
|
|
)
|
|
|
|
return ModelResponse(
|
|
content=response.text,
|
|
usage={
|
|
'prompt_tokens': response.usage.input,
|
|
'completion_tokens': response.usage.output,
|
|
},
|
|
model=self.model_name,
|
|
)
|
|
|
|
async def generate_with_tools(
|
|
self,
|
|
messages: list[dict],
|
|
tools: list[dict],
|
|
**kwargs
|
|
) -> ModelResponse:
|
|
"""Generate with tool/function calling support."""
|
|
# Optional: implement if your model supports tool use
|
|
raise NotImplementedError("Tool use not supported")
|
|
```
|
|
|
|
## Step 2: Register the Backend
|
|
|
|
Add to `skillopt/model/__init__.py`:
|
|
|
|
```python
|
|
from .your_backend import YourBackend
|
|
|
|
BACKEND_REGISTRY = {
|
|
# ... existing backends ...
|
|
'your_backend': YourBackend,
|
|
}
|
|
```
|
|
|
|
## Step 3: Configure
|
|
|
|
Use your backend in any config:
|
|
|
|
```yaml
|
|
model:
|
|
backend: your_backend
|
|
model_name: your-model-id
|
|
temperature: 0.7
|
|
max_tokens: 4096
|
|
```
|
|
|
|
Set credentials via environment variable:
|
|
|
|
```bash
|
|
export YOUR_API_KEY="your-key"
|
|
```
|
|
|
|
## Required Interface
|
|
|
|
Your backend must implement these methods:
|
|
|
|
| Method | Required | Description |
|
|
|---|---|---|
|
|
| `generate()` | ✅ | Basic text generation |
|
|
| `generate_with_tools()` | Optional | Tool/function calling |
|
|
| `count_tokens()` | Optional | Token counting for context management |
|
|
|
|
## Tips
|
|
|
|
!!! tip
|
|
- Test your backend with `python -c "from skillopt.model.your_backend import YourBackend"` first
|
|
- Use `async` methods for all API calls — SkillOpt uses asyncio throughout
|
|
- Implement retry logic with exponential backoff for production use
|
|
- Add your API key to `.env.example` when submitting a PR
|