Skip to main content

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 — quatro 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
  • Completion — o que invoke() retorna quando o próprio servidor chama o modelo

Dois caminhos de uso, que podem conviver na mesma aplicação:

Caminho Quem chama o modelo Quando usar
render() / render_messages() a aplicação, com o SDK que preferir Você já tem um cliente de LLM, quer streaming, tools, ou o prompt precisa funcionar offline
invoke() o servidor, pelo proxy LiteLLM Você só quer a resposta: sem SDK de LLM, sem credenciais de provedor na aplicação

Instalação

O pacote está publicado no PyPI como prompt-manager-client-dl:

uv add prompt-manager-client-dl
# ou: pip install prompt-manager-client-dl

O nome de import continua sendo prompt_manager. O código-fonte vive neste repositório, em client/ — para desenvolver contra ele, instale como dependência de path/git:

uv add "prompt-manager-client-dl @ 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:

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 — e levanta PromptValidationError se o prompt define mensagens de chat. Para esses (ou para padronizar tudo em mensagens), use render_messages(), que aceita os mesmos argumentos e retorna uma lista [{"role", "content"}] no formato OpenAI:

messages = pm.render_messages(
    "resposta_juridica",
    variables={"pergunta": pergunta, "contexto": contexto},
)
# → [{"role": "system", "content": "..."}, {"role": "user", "content": "..."}]

# pronto para qualquer SDK no formato OpenAI (openai, litellm, ...):
resposta = client.chat.completions.create(model=..., messages=messages)

# LangChain aceita esses dicts diretamente:
resposta = chat_model.invoke(messages)

Um prompt sem marcadores de papel vira uma única mensagem system — então render_messages() pode ser o único caminho da sua aplicação, sem ramificar pelo formato do prompt.

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:

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

Async

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:

prompt = pm.get_prompt("resposta_juridica")

prompt.template          # texto resolvido completo (fragmentos 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

prompt.messages          # o template já dividido em mensagens de chat pelo servidor
prompt.is_chat           # True se o template usa marcadores de papel

rendered = prompt.render(pergunta="...", contexto="...")            # prompts de texto
messages = prompt.render_messages(pergunta="...", contexto="...")   # qualquer prompt

render e render_messages (tanto no cliente quanto no Prompt) validam 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.

Invocar o modelo pelo servidor (invoke)

invoke() faz tudo de uma vez do lado do servidor: resolve o prompt, preenche as variáveis, envia as mensagens ao proxy LiteLLM do Prompt Manager e devolve a resposta. A aplicação não precisa de SDK de LLM nem de credenciais de provedor:

completion = pm.invoke(
    "resposta_juridica",
    variables={"pergunta": pergunta, "contexto": contexto},
    model="openai/gpt-4o-mini",          # opcional: padrão é o modelo sugerido na versão
    params={"temperature": 0.2},         # opcional: mescla por cima dos parâmetros sugeridos
    request_id=request_id, user=user_email, metadata={"tenant": tenant},
)

completion.text        # a resposta do modelo ("" se não houver texto)
completion.content     # o mesmo, mas None quando o modelo não devolveu texto (ex.: só tool call)
completion.result      # o payload completo do proxy, no formato OpenAI (choices, usage, ...)
completion.usage       # atalho para result["usage"] — tokens de prompt/resposta
completion.messages    # exatamente o que foi enviado ao modelo
completion.model       # modelo e parâmetros efetivamente usados
completion.params
completion.version     # versão do prompt e hash do snapshot — registre-os junto da resposta
completion.hash
completion.usage_recorded  # True quando o servidor gravou o caso na tela de Registros

Os argumentos são os mesmos de render_messages() (variables, environment, fewshots, request_id, user, metadata), mais:

Argumento Padrão Significado
model modelo sugerido na versão Nome do modelo no proxy LiteLLM (GET /api/v1/models lista os disponíveis). Sem sugestão e sem model, o servidor responde 422
params {} Parâmetros do chat completion (temperature, max_tokens, response_format, …). Mesclados por cima dos sugeridos na versão; os seus vencem. stream é recusado
version None Fixa uma versão commitada específica em vez de seguir o ambiente
litellm_api_key o do construtor Chave virtual LiteLLM só para esta chamada (veja abaixo)
timeout 180 Segundos que o servidor espera pelo modelo (máx. 600); o cliente espera esse tempo mais o timeout HTTP do construtor

Variáveis obrigatórias faltando são validadas no servidor, com as mesmas mensagens do playground (nomes desconhecidos em variables são ignorados, diferente de render()). Fragmentos não podem ser invocados (use get_fragment_text()).

Chave LiteLLM própria

Por padrão o servidor chama o proxy com a chave dele. Se a sua aplicação tem uma chave virtual LiteLLM própria (para custo, cotas ou modelos liberados por time), informe-a e a chamada é feita — e cobrada — com ela:

pm = PromptManager(application="superchat", litellm_api_key="sk-...")     # todas as chamadas
pm.invoke("resposta_juridica", variables=v, litellm_api_key="sk-...")     # só esta

Sem argumento, o cliente lê a variável de ambiente PROMPT_MANAGER_LITELLM_API_KEY. A chave viaja no header X-LiteLLM-API-Key, nunca no corpo JSON. Uma chave recusada pelo proxy chega como PromptInvocationError com status == 401.

Erros e resiliência

invoke() não usa cache nem fallback offline — uma chamada de modelo precisa do servidor de qualquer forma. Qualquer falha vira PromptInvocationError, com .status (o HTTP status que o Prompt Manager respondeu, None se ele estava inacessível) e .detail (o corpo da resposta):

status Significado
404 Prompt inexistente ou arquivado
422 Variável obrigatória faltando, sem modelo, prompt é um fragmento, stream nos params
4xx do proxy (ex.: 401, 400, 429) O proxy LiteLLM recusou a chamada; detail traz litellm_status e litellm_error com a resposta original
502 Proxy inacessível ou erro 5xx nele
503 O servidor não tem proxy LiteLLM configurado
504 O modelo não respondeu dentro do timeout

Disponível também no AsyncPromptManager (await pm.invoke(...)).

Fragmentos

Fragmentos (blocos reutilizáveis: modelos de documento, seções de instrução, personas) são servidos pelo mesmo /fetch que os prompts — get_prompt() funciona para ambos e prompt.kind distingue ("prompt" ou "fragment"). Para o caso comum — a aplicação quer só o texto do fragmento, mantendo os {{placeholders}} para preencher depois — use get_fragment_text():

modelo = pm.get_fragment_text("modelo-contestacao")             # placeholders intactos
modelo = pm.get_fragment_text("modelo-contestacao", values={"esfera": "civel"})

Sub-fragmentos e bancos de exemplos chegam expandidos pelo servidor; ramos {% if %} são resolvidos localmente com os values informados (os defaults registrados preenchem os omitidos); marcadores de papel são descartados e o texto vem inteiro. Diferente de render(), nada é obrigatório — placeholders restantes são devolvidos como estão. Cache, revalidação por ETag e fallback offline funcionam exatamente como em get_prompt(); fragmentos compartilhados vivem na pseudo-aplicação shared (PromptManager(application="shared", ...)). Disponível também no AsyncPromptManager (await pm.get_fragment_text(...)).

Mensagens de chat

Um template pode conter linhas com marcadores de papel {% system %} / {% user %} / {% assistant %} — uma nova mensagem começa em cada marcador, e o conteúdo antes do primeiro pertence a system. O servidor já entrega o template dividido (prompt.messages), e render_messages() preenche as variáveis mantendo os papéis. render() recusa prompts de chat (PromptValidationError) em vez de devolver texto com marcadores embutidos; se você realmente quiser o texto plano, junte os conteúdos de render_messages() deliberadamente. Snapshots de fallback antigos (sem o campo messages) continuam funcionando: o cliente divide o template localmente com a mesma regra do servidor.

Ambientes

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

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.

Fluxo ({% if %} no template)

Não existe uma classe separada de variáveis de "controle": as booleanas e categóricas que decidem os ramos {% if %} são variáveis comuns, passadas em variables junto com as de texto. O servidor entrega o template com as tags {% if %} intactas e a biblioteca resolve os ramos localmente a cada render; a mesma variável também pode ser impressa como {{placeholder}} (booleanas viram true/false). Uma busca e uma entrada de cache cobrem todas as combinações.

messages = pm.render_messages(
    "chitchat",
    variables={
        "agente_customizado": True,        # booleana → decide o ramo
        "agent_description": descricao,    # variável de texto → preenche {{...}}
        "query": pergunta,
        ...
    },
)

Uma booleana/categórica omitida usa o valor padrão registrado no manager — a mesma regra do servidor, então o texto resultante é byte a byte o que o playground mostra para os mesmos valores. Variáveis cujo único uso está em um ramo desligado deixam de ser obrigatórias (continuam aceitas, apenas não são usadas). Snapshots de fallback gerados pelo prompt-manager pull preservam as tags, então o fallback offline também cobre todas as combinações.

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:

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 e, em último caso, <fallback_dir>/<nome>.default.json — assim um bundle com apenas snapshots default continua útil em prod/staging.

Só quando os quatro falham ele levanta PromptUnavailableError. O resultado obsoleto/de fallback também entra no cache com TTL, então uma indisponibilidade custa no máximo um timeout por janela de TTL, não um por chamada.

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):

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()/render_messages() 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. invoke() produz o mesmo registro, gravado pelo próprio servidor depois de responder.

  • 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) — vale para render*() e invoke() —, 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/invoke para a tela de Registros
litellm_api_key $PROMPT_MANAGER_LITELLM_API_KEY, senão None Chave virtual LiteLLM enviada em invoke(); None usa a chave do servidor

Release files for prompt-manager-client-dl 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for prompt-manager-client-dl 0.7.0
File Size Uploaded
prompt_manager_client_dl-0.7.0.tar.gz 30.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for prompt-manager-client-dl 0.7.0
File Interpreter ABI Platform
prompt_manager_client_dl-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 54.5 kB

Release files / prompt_manager_client_dl-0.7.0.tar.gz

Download URL prompt_manager_client_dl-0.7.0.tar.gz
Size 30.4 kB
Tags Source
SHA-256 checksum
How to use checksums
e1abd6eb7768c47128bbda59c7a692bed51ef5d4250de96ab12eb4f08fd352c9
BLAKE2b-256 checksum
How to use checksums
65c82f456f01581aabb1b40f04f8fb0a1a7554cb0a13b9a64eefac54d39bc2d6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / prompt_manager_client_dl-0.7.0-py3-none-any.whl

Download URL prompt_manager_client_dl-0.7.0-py3-none-any.whl
Size 24.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3bd6faae9d4d3390c26113ed4d1a9b3aaa0315249b97dadfb8c892642ee2cbcb
BLAKE2b-256 checksum
How to use checksums
1e506ad412dd5ff76cc1ed6f8956db06e7c5b2db499102de1fe202660569a60f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.9.30 {"installer":{"name":"uv","version":"0.9.30","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

0.8.0

2 release files

This release

0.7.0 This release

2 release files

0.6.0

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page