Metadata-Version: 2.5
Name: prompt-manager-client-dl
Version: 0.1.0
Summary: Live-updating client for Prompt Manager
Requires-Python: >=3.10
Requires-Dist: cachetools<7,>=5
Requires-Dist: httpx<1,>=0.27
Requires-Dist: requests<3,>=2.31
Description-Content-Type: text/markdown

# Prompt Manager — Cliente Python

`prompt-manager-client` é a biblioteca que as aplicações de IA importam para buscar seus prompts na API do Prompt Manager em tempo de execução. Os prompts se atualizam ao vivo: quando alguém commita e publica uma nova versão na UI, a aplicação passa a usá-la em segundos, sem redeploy.

Ela é deliberadamente pequena — três coisas públicas:

- `PromptManager` — cliente síncrono (`requests`)
- `AsyncPromptManager` — cliente assíncrono (`httpx`), mesma interface com `await`
- `Prompt` — o prompt imutável e totalmente resolvido que uma busca retorna

## Instalação

O pacote vive neste repositório, em `client/`. Adicione-o como dependência de path/git, por exemplo com uv:

```bash
uv add "prompt-manager-client @ git+ssh://git@bitbucket.org/avisourgente/prompt-manager.git#subdirectory=client"
```

Requer Python ≥ 3.10. Dependências: `requests`, `httpx`, `cachetools`.

## Começo rápido

Crie **um cliente por processo** na inicialização (ele guarda o cache e a sessão HTTP) e reutilize-o:

```python
from prompt_manager import PromptManager

pm = PromptManager(
    application="superchat",                 # o nome da sua aplicação no manager
    environment="prod",                      # qual deployment seguir
    fallback_dir="prompts_fallback",         # opcional: snapshots offline (veja abaixo)
)

# Uma chamada: busca (com cache) + preenche variáveis + relata o uso
text = pm.render(
    "resposta_juridica",
    variables={"pergunta": pergunta, "contexto": contexto},
    request_id=request_id,        # campos opcionais de observabilidade —
    user=user_email,              # eles vinculam o caso real ao prompt
    metadata={"tenant": tenant},  # na tela de "Registros" do manager
)
```

`render()` retorna a string final pronta para enviar ao modelo. Se o prompt define mensagens de chat, você provavelmente quer `get_prompt()` (abaixo).

### Onde o cliente se conecta (`base_url`)

O endereço da API é resolvido nesta ordem:

1. o argumento `base_url` do construtor, se informado;
2. a variável de ambiente **`PROMPT_MANAGER_URL`**;
3. o padrão de produção: **`http://promptmanager-prod.datalawyer.local`**.

Ou seja: em produção você não configura nada, e deployments de staging/dev redirecionam todos os clientes com uma única variável de ambiente:

```bash
export PROMPT_MANAGER_URL=http://localhost:8000   # ex.: stack de dev local
```

### Async

```python
from prompt_manager import AsyncPromptManager

pm = AsyncPromptManager(application="superchat", environment="prod")

async def handle(question: str) -> str:
    return await pm.render("resposta_juridica", variables={"pergunta": question})
```

Use-o como async context manager (`async with AsyncPromptManager(...) as pm:`) ou mantenha-o pela vida do processo; ao sair, ele descarrega os relatos de uso pendentes e fecha seu cliente `httpx` (a menos que você tenha passado o seu próprio).

## Trabalhando com o objeto `Prompt`

`get_prompt()` retorna o prompt resolvido sem renderizá-lo:

```python
prompt = pm.get_prompt("resposta_juridica")

prompt.template          # texto resolvido completo (fragmentos, controles e exemplos já expandidos)
prompt.variables         # as variáveis de runtime que o template ainda espera
prompt.version           # número da versão commitada que esta resolução usou
prompt.hash              # hash do snapshot — registre-o para rastrear qualquer chamada de LLM até o prompt exato
prompt.suggested_model   # modelo + parâmetros sugeridos pelo autor do prompt
prompt.suggested_params
prompt.fragment_versions # quais versões de fragmentos foram embutidas
prompt.fewshots          # quais bancos de exemplos foram injetados, e quantos exemplos cada um contribuiu

rendered = prompt.render(pergunta="...", contexto="...")
```

`render` (tanto no cliente quanto no `Prompt`) valida os valores: nomes de variáveis desconhecidos, variáveis obrigatórias faltando e `{{placeholders}}` restantes levantam `PromptValidationError`. Valores dict/list são codificados em JSON automaticamente.

### Mensagens de chat

Um template resolvido pode conter linhas com marcadores de papel `{% system %}` / `{% user %}` / `{% assistant %}`. O cliente mantém o template plano (os marcadores sobrevivem intactos ao `render()`); para prompts com múltiplas mensagens, divida você mesmo o texto renderizado nessas linhas de marcador: uma nova mensagem começa em cada marcador, e o conteúdo antes do primeiro marcador pertence a `system`. A resposta do `/fetch` da API também carrega um array `messages` já dividido, se você preferir consumi-lo diretamente.

## Ambientes

O `environment` informado na construção é o padrão; qualquer chamada pode sobrescrevê-lo:

```python
pm.get_prompt("resposta_juridica", environment="staging")
```

`"default"` é especial: ele sempre segue a **versão commitada mais recente**, sem precisar de deployment explícito. Ambientes nomeados (`prod`, `staging`, …) servem a versão que foi fixada neles pela UI — e caem para a versão mais recente se o prompt nunca foi fixado ali.

## Ablação de few-shots (A/B sem exemplos)

`fewshots=False` resolve o mesmo prompt com todos os bancos de exemplos desligados — é assim que uma aplicação mede o que os exemplos realmente valem:

```python
com_exemplos = pm.render("classificador", variables=v)                    # normal
sem_exemplos = pm.render("classificador", variables=v, fewshots=False)   # ablação
```

As duas resoluções têm cache e ETag separados.

## Cache, resiliência e fallback offline

O cliente é construído para que a API de prompts nunca seja um ponto único de falha:

1. **Cache com TTL** (padrão 45 s, `ttl=` no construtor): buscas repetidas dentro da janela não custam nada.
2. **Revalidação por ETag**: expirado o TTL, o cliente revalida com `If-None-Match`; um prompt inalterado custa um 304, não um novo download.
3. **Último valor conhecido**: se a API estiver inacessível, o cliente registra um warning e continua servindo o último prompt buscado com sucesso (por chave nome/ambiente/fewshots), pela vida do processo.
4. **Arquivos de fallback locais**: se a API estiver inacessível *e* nada foi buscado ainda (ex.: logo após um cold start), o cliente carrega `<fallback_dir>/<nome>.<ambiente>.json`, depois `<fallback_dir>/<nome>.json`.

Só quando os quatro falham ele levanta `PromptUnavailableError`.

Gere os snapshots de fallback com a CLI incluída (commite-os no seu repositório ou embuta-os na sua imagem, atualizando a cada deploy):

```bash
uv run prompt-manager pull --app superchat --env prod --out prompts_fallback/
```

(`--url` sobrescreve o endereço da API; caso contrário valem `PROMPT_MANAGER_URL` / o padrão de produção, igual à biblioteca.)

## Relato de uso

Cada `render()` dispara (fire-and-forget) um relato para `POST /api/v1/usage` com a identidade do prompt (nome, versão, hash do snapshot) e os valores de variáveis com que foi preenchido. A UI do manager os lê de volta para que os prompts possam ser testados contra **casos reais de produção** em vez de casos inventados.

- Nunca bloqueia nem quebra um render — falhas são logadas em nível debug e descartadas.
- Passe `request_id`, `user` e `metadata` para tornar os casos gravados filtráveis.
- Desabilite completamente com `report_usage=False` (ex.: em testes), ou no servidor deixando `OPENSEARCH_HOST` vazio.

## Referência do construtor

| Parâmetro | Padrão | Significado |
|-----------|--------|-------------|
| `application` | *(obrigatório)* | Nome da aplicação registrada no manager |
| `base_url` | `$PROMPT_MANAGER_URL`, senão `http://promptmanager-prod.datalawyer.local` | Raiz da API do Prompt Manager |
| `environment` | `"default"` | Ambiente padrão de todas as chamadas |
| `ttl` | `45` | Segundos que um prompt buscado é servido sem revalidação |
| `fallback_dir` | `None` | Diretório com snapshots offline do `prompt-manager pull` |
| `timeout` | `10` | Timeout HTTP em segundos |
| `session` / `client` | `None` | Traga seu próprio `requests.Session` / `httpx.AsyncClient` |
| `report_usage` | `True` | Relata as variáveis de cada render para a tela de Registros |
