Metadata-Version: 2.4
Name: ai-generator
Version: 0.1.0
Summary: Roteador de chamadas LLM por model/tier, multi-provider, com retorno consistente.
Author-email: Stamatios Stamou Jr <bushier.outsets.0c@icloud.com>
License: MIT
Keywords: llm,router,openai,gemini,anthropic,multi-provider
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx>=0.24
Requires-Dist: requests>=2.28
Provides-Extra: openai
Requires-Dist: openai>=1.30; extra == "openai"
Provides-Extra: gemini
Requires-Dist: google-genai>=0.3; extra == "gemini"
Provides-Extra: claude
Requires-Dist: anthropic>=0.40; extra == "claude"
Provides-Extra: cerebras
Requires-Dist: cerebras-cloud-sdk>=1.0; extra == "cerebras"
Provides-Extra: all
Requires-Dist: openai>=1.30; extra == "all"
Requires-Dist: google-genai>=0.3; extra == "all"
Requires-Dist: anthropic>=0.40; extra == "all"
Requires-Dist: cerebras-cloud-sdk>=1.0; extra == "all"
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# aigen

Roteador de chamadas LLM por **model/tier**, multi-provider, com **call/return consistente**.

Você escolhe um *tier* (`basic`/`advanced`/`pro`) ou uma key `model:provider`;
o pacote resolve o provider, instancia, chama e devolve sempre um dataclass
padronizado. Trocar de modelo ou provider = editar config, não código.

## Instalação

SDKs de provider são **extras opcionais** — instale só os que usar:

```bash
pip install "aigen[openai]"        # OpenAI, Groq, z.ai, Kimi, Alibaba, DeepSeek (openai-compat)
pip install "aigen[gemini]"        # Google Gemini
pip install "aigen[claude]"        # Anthropic Claude
pip install "aigen[cerebras]"      # Cerebras
pip install "aigen[all]"           # tudo

# Desenvolvimento (editável):
pip install -e "/Users/ssjunior/Dev/llm-router[all]"
```

Providers que falam HTTP puro (Kie, Minimax, imagens da Alibaba/z.ai, STT da Groq)
funcionam só com o core (`httpx`, `requests`).

## Uso

Passe `tier` ('basic'/'advanced'/'pro') **ou** `model` ('model:provider'). `tier`
default é `'basic'`; `model=` sobrepõe. `prompt` é o 1º posicional; o resto é keyword.
(stt/tts não têm tabela de tier — exigem `model=`.)

```python
import aigen

# por tier (resolve via constants/tiers.py)
r = aigen.generate_text('Resuma o texto X', tier='basic')
print(r.content)          # JSON parseado (dict/list) ou str
print(r.total_tokens)

# por key explícita
r = aigen.generate_text('Resuma X', model='gemini-2.5-flash:gemini', system_instruction='...')

# timeout por chamada (texto) — default LLM_REQUEST_TIMEOUT (900s)
r = aigen.generate_text('Resuma X', tier='basic', timeout=60)
```

### Outras mídias

```python
img   = aigen.generate_image('um gato astronauta', tier='pro', files=[...])
video = aigen.generate_video('clipe contínuo de...', tier='basic', task='text_to_video')
music = aigen.generate_music('lo-fi chill', tier='pro', instrumental=True)
text  = aigen.transcribe(audio_url, model='whisper-large-v3:groq')   # stt/tts exigem model=
```

### Entry genérico (fila/worker)

Recebe o payload e o `mode` e despacha — mesma engine, mesmo tratamento de erro.
`payload` é o 1º posicional; `mode` é keyword com default `'text'`:

```python
r = aigen.generate(prompt)                                          # mode='text'
r = aigen.generate(prompt, mode='video', tier='basic', task='image_to_video')
r = aigen.generate(audio_url, mode='stt', model='whisper-large-v3:groq')
```

`mode`: `text` · `image` · `video` · `music` · `stt` · `tts`.
`task` (só vídeo): `text_to_video` · `image_to_video`.

## Contrato de retorno (uniforme)

**Sucesso** → retorna o dataclass do tipo. **Falha** → levanta exception tipada.
Igual para todas as mídias.

| Mídia | Função | Retorno (sucesso) |
|-------|--------|-------------------|
| Texto | `generate_text` | `TextResponse` |
| Imagem | `generate_image` | `ProviderResponse` |
| Vídeo | `generate_video` | `VideoResponse` |
| Música | `generate_music` | `TTMResponse` |
| STT | `transcribe` | `STTResponse` |
| TTS | `synthesize` | `TTSResponse` |

### Taxonomia de erro

```
ProviderError                 # base — terminal
├─ ProviderBlocked            # conteúdo barrado — .category: 'safety'|'copyright'|'other'
└─ ProviderRetryable          # transitório (pode retry)
   ├─ ProviderRateLimit       # 429
   └─ ProviderTimeout         # timeout / rede
```

Trata-se igual para qualquer mídia, num lugar só:

```python
try:
    r = aigen.generate_video(prompt, tier='basic')
    use(r.videos)
except aigen.ProviderBlocked as e:
    print('barrado:', e.category, e)        # copyright / safety
except aigen.ProviderRetryable as e:
    retry()                                  # rede / ratelimit / timeout
except aigen.ProviderError as e:
    fail(e)                                  # terminal genérico
```

Cada exception carrega `.provider`, `.model`, `.status_code`, `.response_time`, `.raw_error`.

## Configuração

Duas coisas distintas:

- **Catálogo técnico** (`aigen/constants/{text,image,audio,video}.py`) — *fatos*
  sobre providers: `api_key_env`, capabilities, créditos. Muda raro.
- **Roteamento por tier** (`aigen/tiers.py`) — a *decisão* de qual modelo cada
  tier usa. Muda toda hora e difere por app.

O roteamento é **app-owned**: `aigen/tiers.py` traz só os defaults (pra funcionar
out-of-box); o **app é o dono** e sobrescreve no boot. Não edite o pacote instalado.

```python
import aigen

# entry único — merge sobre os defaults (tiers não passados continuam valendo)
aigen.configure(
    text={'basic': 'gemini-2.5-flash:gemini', 'pro': 'claude-sonnet-4-6:claude'},
    image={'pro': 'gpt-image-2:openai'},
    video={'basic': {'text_to_video': 'bytedance/seedance-1.5-pro:kie'}},
)

# replace=True → o app vira dono total daquela mídia (zera antes)
aigen.configure(text={'basic': 'glm-5.2:zai'}, replace=True)

# ajustes pontuais
aigen.set_tier('text', 'pro', 'gpt-5.4:openai')

# registrar provider/modelo novo no catálogo
aigen.register_provider('text', 'meu_provider', 'MEU_API_KEY', models={
    'meu-modelo': {'tier': 'pro', 'base_credits': 8},
})
aigen.register_model('text', 'openai', 'gpt-5.4', {'tier': 'pro', 'base_credits': 8})
```

> Lugar claro e visível: um arquivo de config **no seu app** (ex.
> `settings/ai_tiers.py`) chamando `aigen.configure(...)` no boot — versionado
> junto do app, perto do resto da config dele.

## API keys

Cada provider lê a key de uma variável de ambiente (campo `api_key_env` no catálogo).
Defaults: `GOOGLE_API_KEY`, `OPENAI_API_KEY`, `CLAUDE_API_KEY`, `GROQ_API_KEY`,
`CEREBRAS_API_KEY`, `ZAI_API_KEY`, `MOONSHOT_AI_API_KEY`, `DASHSCOPE_API_KEY`,
`DEEPSEEK_API_KEY`, `MINIMAX_API_KEY`, `KIE_API_KEY`.

## Logs

Cada chamada emite no início (antes de bloquear) e no fim. Útil pra não ficar no
escuro durante esperas longas:

```
generate START | mode=text model=glm-5.2:zai timeout=900
Z.AI text | model=glm-5.2 in=812 out=145 reasoning=0 total=957 time=3.41s
```

O pacote só usa `logging.getLogger(__name__)` — **não** configura handler. Pra ver
na execução, o app habilita logging (uma vez, no boot):

```python
import logging
logging.basicConfig(level=logging.INFO)            # console (stderr), ao vivo
# logging.basicConfig(level=logging.INFO, filename='aigen.log')   # só arquivo
```

Sem isso, INFO vai pro vazio. As stats de fim podem ser desligadas com
`LOG_PROVIDER_STATS=0`; o log START fica.

## Providers de texto

gemini · openai · claude (API e CLI) · codex_cli · groq · cerebras · deepseek ·
zai · kimi · minimax · alibaba · kie
