Arquitetura de referência: Implemente o Langfuse no MKS da OVHcloud para a observabilidade de LLM e o rastreio de custos de IA

Contexto
O AI Endpoints da OVHcloud fornece uma API compatível com a OpenAI que dá às aplicações acesso a um vasto catálogo de modelos de peso aberto, incluindo os modelos Qwen, Llama, Mistral, gpt-oss, de embedding, guard e voz, sem que as equipas tenham de gerir a infraestrutura de inferência. É a camada de inferência baseada no uso monitorizada por esta arquitetura.
O Managed Kubernetes Service (MKS) da OVHcloud elimina a carga operacional associada à gestão do plano de controlo Kubernetes: os conjuntos de nós, as atualizações e a alta disponibilidade são geridos pela OVHcloud, e mantém o controlo total sobre o que é executado nos nós de trabalho. É o local ideal para executar uma aplicação sem estado, como os processos web e de trabalho do Langfuse, enquanto as componentes com estado (bases de dados, armazenamento de objetos) residem nos serviços geridos da OVHcloud.
O Langfuse fornece a camada de observabilidade para aplicações que utilizam o AI Endpoints. Como a integração com o SDK da OpenAI é uma substituição direta, direcionar uma base de código existente compatível com a OpenAI para o AI Endpoints e obter um tracing completo a partir dela é uma alteração de apenas duas linhas, não uma reescrita.

Langfuse no MKS da OVHcloud para observabilidade de LLM e rastreio de consumo de tokens
1. Fluxo de dados
Juntando todos os elementos, um pedido rastreado flui da seguinte forma:

Fluxo de dados
1. A aplicação faz uma chamada direta para o AI Endpoints
O cliente drop-in openai.OpenAI() envia o pedido diretamente para a API de inferência – o Langfuse fica totalmente fora desta chamada, pelo que uma indisponibilidade do Langfuse nunca afeta a capacidade de a aplicação obter uma resposta.
2. O AI Endpoints transmite a resposta de volta
O chunk final contém os dados de utilização de tokens.
3. O SDK exporta o que acabou de ver como um span batch do OpenTelemetry
Isto é feito de forma assíncrona, via HTTPS, para /api/public/otel/v1/traces na instância Langfuse.
Este processo é executado em segundo plano e não adiciona qualquer latência à resposta que a aplicação já recebeu no passo 2.
4. O Langfuse web ingere o batch
Verifica a chave API do pedido em relação ao seu projeto no Postgres (um lookup em cache no Valkey, não uma consulta nova a cada chamada), escreve o batch bruto tal como está no armazenamento de objetos e envia apenas uma referência ao mesmo para uma fila do Valkey.
5. O processo de trabalho esvazia essa fila de acordo com o seu próprio agendamento, dissociado de qualquer pedido específico
Extrai a referência do Valkey, recupera o lote completo do armazenamento de objetos, resolve a entrada de Gestão de Prompts a que está associado (se houver) através do Postgres e persiste os registos de trace e geração no ClickHouse (modelo, contagens de tokens, latência, preços)
6. O processo de trabalho também trata de tudo o que esteja agendado ou em lote
As exportações em lote e os carregamentos de multimédia chegam ao S3 independentemente de quando o pedido original ocorreu.
⚠️ Nota: Nada a partir do passo 3 pode adicionar latência ao pedido da aplicação nos passos 1-2; essa transferência assíncrona e o facto de o Langfuse web nunca bloquear à espera do Postgres ou do ClickHouse para confirmar um lote são a razão pela qual o tracing não tem qualquer custo no caminho crítico.
2. API do AI Endpoints
Tudo o que se segue chama uma de duas API AI Endpoints da OVHcloud:
- API de inferência: https://oai.endpoints.kepler.ai.cloud.ovh.net/v1
Esta é a API com que a sua aplicação comunica efetivamente. Implementa a superfície da API OpenAI, pelo que qualquer SDK compatível com OpenAI funciona com ela ao alterar o base_url e a chave da API:
import openai
client = openai.OpenAI(
api_key="<your-ai-endpoints-token>",
base_url="https://oai.endpoints.kepler.ai.cloud.ovh.net/v1",
)
response = client.chat.completions.create(
model="Qwen3.5-397B-A17B",
messages=[{"role": "user", "content": "hello"}],
)Se recuperar o esquema OpenAPI diretamente do gateway (GET /openapi.json), verá que a interface disponível vai muito além das sugestões de chat.
É útil chamar diretamente a API GET /v1/models pelo menos uma vez: ela devolve todos os modelos aos quais o seu token dá acesso.
A secção seguinte centra-se na API de catálogo, que lhe permite obter mais informações sobre os modelos, incluindo detalhes acerca dos preços, indispensáveis para o acompanhamento de custos.
- API de catálogo: https://catalog.endpoints.ai.ovh.net/rest/v1/models_v2
Esta é a API que alimenta a página do catálogo web AI Endpoints; é um pedido GET simples que não requer autenticação, o qual devolve a lista completa de modelos juntamente com os metadados apresentados na interface de utilizador do catálogo: descrição, pontuações de referência, editor, licença, tamanho do contexto, ligações para o playground e documentação, bem como um bloco usage_information.pricing:
curl -s https://catalog.endpoints.ai.ovh.net/rest/v1/models_v2 | jq '.[0]'Devolução:
{
"id": "qwen-3-5-397b",
"name": "Qwen3.5-397B-A17B",
"category": "Visual LLM",
"metadata": {
"aliases": ["Qwen/Qwen3.5-397B-A17B", "qwen3.5-397b-a17b"],
"context": "262k",
"usage_information": {
"pricing": [
{"price": 0.6, "price_unit": "million_input_tokens"},
{"price": 3.6, "price_unit": "million_output_tokens"}
]
}
}
}As duas API são complementares:
- a API de catálogo é uma extração pontual ou periódica usada para criar tabelas de preços locais e metadados
- a API de inferência é o que uma aplicação em execução chama em cada pedido
As duas API são complementares:
- a API de catálogo é uma extração pontual ou periódica usada para criar tabelas de preços locais e metadados
- a API de inferência é o que uma aplicação em execução chama em cada pedido

API de inferência e de catálogo AI Endpoints
⚠️ Nota: tenha em atenção que os preços do AI Endpoints estão em euros.
Pré-requisitos
Antes de começar, certifique-se de que tem:
- Uma conta OVHcloud Public Cloud
- Um utilizador OpenStack com a função de Administrador
- Uma chave de API AI Endpoints
- Um nome de domínio que possa apontar para um distribuidor de carga
- o kubectl instalado e o helm instalado (pelo menos a versão 3.x)
Agora que tem todos os ingredientes, pode implementar o Langfuse com o MKS da OVHcloud e outros serviços geridos!
Guia de arquitetura: Implementação do Langfuse nos serviços geridos OVHcloud Public Cloud
Passo 1 – Aprovisionar o cluster Kubernetes e os serviços geridos OVHcloud
Os processos web e de trabalho do Langfuse são, por si só, leves. Portanto, a escolha dos recursos depende quase inteiramente dos serviços geridos que os rodeiam. De seguida encontrará a explicação dos passos práticos envolvidos na configuração de cada um deles.
Tudo o que se segue é criado no OVHcloud Manager, na secção Public Cloud do seu projeto.
1. Criar cluster MKS e pools de nós
1.1. Configurar cluster
A partir da Área de Cliente OVHcloud, crie um cluster Kubernetes utilizando o MKS:
- Nome: langfuse-cluster
- Localização: Região 1-AZ – Gravelines (GRA11)
- Plano: Gratuito (ou Standard)
- Rede: anexe uma rede privada (por ex., 0000 - AI Private Network)
- Versão: Versão estável mais recente (por ex., 1.35)
1.2. Criar pools de nós
Durante a criação do cluster, configure os 2 pools de nós.
O primeiro é o np-system. Executa componentes ao nível do cluster, incluindo o Traefik (ingress) e o cert-manager (certificados TLS), bem como os daemonsets do sistema Kubernetes predefinidos.
- Nome do pool de nós: np-system
- Flavor: B3-8
- Número de nós: 3
- Dimensionamento automático: Desativado (OFF)
O segundo é o np-workload, dedicado aos pods da aplicação Langfuse (web + worker).
- Nome do pool de nós: np-workload
- Flavor: B3-16
- Número de nós: 1
- Dimensionamento automático: Desativado (OFF)
1.3. Configurar o acesso ao Kubernetes
Após o aprovisionamento dos seus nós, pode transferir o ficheiro Kubeconfig
# configure kubectl with your MKS cluster
export KUBECONFIG=/path/to/your/kubeconfig-xxxxxx.yml
# verify cluster connectivity
kubectl cluster-info
kubectl get nodes
Returns:
NAME STATUS ROLES AGE VERSION
np-system-node-388e55 Ready <none> 12d v1.35.2
np-system-node-4c5326 Ready <none> 12d v1.35.2
np-system-node-93bc1c Ready <none> 12d v1.35.2
np-workload-node-3a76f7 Ready <none> 12d v1.35.22. Configurar bases de dados
2.1. Base de dados Postgre para dados transacionais Langfuse
Na secção Bases de dados do OVHcloud Public Cloud, clique em Criar um serviço e configure-o da seguinte forma:
- Motor: PostgreSQL
- Versão: 17
- Localização: GRA
- Plano de serviço: Business
- Instância: Db1-4
- Armazenamento: 80 GB (predefinição)
- Rede: Rede pública (internet)
Guarde as seguintes informações em segurança antes de passar para o passo seguinte:
- host
- porta
- nome da base de dados
- nome de utilizador
- palavra-passe
- URI
2.2. Base de dados Valkey para colocação de tarefas em cache e fila de espera
Na secção Bases de dados do OVHcloud Public Cloud, clique em Criar um serviço e configure-o da seguinte forma:
- Motor: Valkey
- Versão: 8.1
- Localização: GRA
- Plano de serviço: Business
- Instância: Db1-4
- Armazenamento: 80 GB (predefinição)
- Rede: Rede pública (internet)
Guarde as seguintes informações em segurança antes de passar para o passo seguinte:
- host
- porta
- nome da base de dados
- nome de utilizador
- palavra-passe
- URI
2.3. ClickHouse, coração dos dados de observabilidade
Na secção Analytics do OVHcloud Public Cloud, clique em Criar um serviço e configure-o da seguinte forma:
- Motor: ClickHouse
- Versão: 25.8
- Localização: EU-WEST-PAR
- Plano de serviço: Production
- Instância: B3-16
- Armazenamento: 100 GB (predefinição)
- Rede: Rede pública (internet)
Guarde as seguintes informações em segurança antes de passar para o passo seguinte:
- host
- porta
- nome da base de dados
- nome de utilizador
- palavra-passe
- URI
⚠ Nota: As bases de dados geridas da OVHcloud (Postgres, Valkey, ClickHouse) recusam todas as ligações por predefinição até que adicione os IP de origem permitidos na página «Restrições de IP» de cada instância no OVHcloud Manager. Não se esqueça de que precisa de adicionar os IP públicos do seu pool de nós MKS às três bases de dados antes de implementar o Langfuse; caso contrário, todas as ligações a partir do cluster irão simplesmente exceder o tempo-limite.
No separador Configuração de cada base de dados, aplique a lista de permissões de IP conforme o exemplo abaixo:

Lista de permissões de IP públicos do pool de nós MKS
3. Criar bucket compatível com S3 como backend de armazenamento
Na secção Object Storage do OVHcloud Public Cloud, crie um container de objetos:
- Tipo de container: API compatível com S3
- Localização: GRA
Guarde as seguintes informações em segurança antes de passar para o passo seguinte:
- nome do bucket
- endpoint
- chave de acesso
- chave secreta
Passo 2 – Configurar namespaces e quota
Crie namespaces dedicados para o ingress controller, o gestor de certificados e o próprio Langfuse, e limite o consumo do namespace do Langfuse para que uma carga de trabalho descontrolada não deite abaixo o resto do cluster.
Primeiro, crie:
De seguida, inicie a implementação.
kubectl apply -f deploy/bootstrap/namespaces.yaml
kubectl apply -f deploy/bootstrap/resourcequota.yamlPasso 3 – Instalar o ingress controller e o gestor de certificados
Esta arquitetura usa o Traefik em vez do ingress-nginx.Os comandos de instalação completos estão incluídos em
deploy/ingress/install.sh. Pode executar:. You can run:
bash deploy/ingress/install.shhelm upgrade --install disponibiliza automaticamente um novo Load Balancer da OVHcloud com um IP público. Aguarde que lhe seja atribuído um antes de prosseguir:
kubectl -n traefik get svc traefik -wcert-manager gere a emissão e renovação de certificados. Instale-o a partir do registo OCI, que tanto a OVHcloud como o upstream recomendam agora em detrimento do repositório Helm antigo.
Transfira o script cert-manager/install.sh e execute: script and run:
bash cert-manager/install.shDe seguida, crie um ClusterIssuer para o Let's Encrypt, utilizando o Traefik como «resolvedor» HTTP-01.
Antes de mais, defina o seu endereço de e-mail no ficheiro cert-manager/cluster-issuer.yaml.
⚠️ Nota: O Let's Encrypt usa este endereço para notificações de expiração, e o registry do cert-manager aceitará tacitamente um valor predefinido se se esquecer de especificar um. Este passo tem de aguardar até que o cert-manager esteja operacional e a ingress class do Traefik esteja implementada, para que o resolvedor possa referenciá-la. Portanto, execute-o assim que as duas instalações descritas acima estiverem concluídas:
kubectl apply -f cert-manager/cluster-issuer.yaml⚠️ Nota: Deve fixar a versão do chart explicitamente (--version). Caso contrário, os dois comandos acima utilizarão, por predefinição, a versão mais recente disponível no momento da instalação, o que significa que a implementação não pode ser reproduzida exatamente ao longo do tempo.
Passo 4 – Configurar o DNS para apontar para o distribuidor de carga
Assim que o serviço Traefik tiver um IP externo, crie um registo A para o hostname onde o Langfuse será disponibilizado:
kubectl -n traefik get svc traefik -o jsonpath='{.status.loadBalancer.ingress[0].ip}'Crie langfuse.<your-domain> -> esse IP na sua zona DNS, e confirme que o mesmo resolve antes de continuar – caso contrário, o desafio HTTP-01 do cert-manager irá falhar de forma silenciosa e tentar outra vez indefinidamente.
Passo 5 – Armazenar credenciais num segredo Kubernetes e configurar os valores Helm
O chart Helm do Langfuse lê todas as credenciais a partir de um segredo em vez de valores em texto simples, pelo que nenhuma informação sensível precisa de residir no ficheiro de valores:
kubectl -n langfuse create secret generic langfuse-secrets \
--from-literal=LANGFUSE_SALT="$(openssl rand -base64 32)" \
--from-literal=LANGFUSE_SECRET_KEY="$(openssl rand -hex 32)" \
--from-literal=NEXTAUTH_SECRET="$(openssl rand -base64 32)" \
--from-literal=POSTGRES_PASSWORD="<your-postgres-password>" \
--from-literal=VALKEY_PASSWORD="<your-valkey-password>" \
--from-literal=CLICKHOUSE_PASSWORD="<your-clickhouse-password>" \
--from-literal=S3_ACCESS_KEY="<your-s3-access-key>" \
--from-literal=S3_SECRET_KEY="<your-s3-secret-key>"O gráfico utilizado é o chart oficial langfuse/langfuse-k8s. Encontrará o ficheiro completo no repositório GitHub public-cloud-examples da OVHcloud: deploy/langfuse/values.yaml
⚠️ Nota: Cada deploy: false indica ao chart para não instalar os seus próprios sub-charts Postgres/Valkey/ClickHouse/MinIO e para se ligar às instâncias geridas da OVHcloud. É isto que torna esta uma implementação de serviços geridos, em vez de uma implementação autogerida.
Passo 6 – Instalar o Langfuse
Obtenha o ficheiro deploy/langfuse/values.yaml do repositório e efetue as alterações necessárias:
- <o-seu-domínio>
- <o-seu-host-postgresql>
- <o-seu-host-valkey>
- <o-seu-host-clickhouse>
- <o-seu-bucket>
Depois, guarde o values.yaml atualizado e, finalmente, execute o deploy/langfuse/install.sh:
bash deploy/langfuse/install.shEsta é a fase final da instalação: requer o namespace e o segredo dos passos 2 e 5, a resolução DNS do passo 4 (caso contrário, o certificado TLS não pode ser emitido) e o ClusterIssuer do passo 3, que já deve existir.
De seguida, verifique se todo o processo de implementação decorreu sem problemas:
kubectl -n langfuse get pods
curl -s https://langfuse.<your-domain>/api/public/health{"status":"OK","version":"<chart appVersion>"} no endpoint de health, e ambos os pods Running sem reinicializações, significa que a pilha está operacional: ingress, TLS, Postgres, Valkey, ClickHouse e S3 estão todos acessíveis.
Passo 7 – Instrumentar uma aplicação
1. Configure o SDK na sua aplicação
Assim que o Langfuse estiver acessível, apontar uma aplicação para ele é uma questão de substituir o SDK, não de reescrever. Para uma aplicação que já utiliza o SDK Python openai com o AI Endpoints:
from dotenv import load_dotenv
load_dotenv() # OPENAI_API_KEY, OPENAI_BASE_URL, LANGFUSE_* must be loaded first
from langfuse.openai import openai # drop-in replacement for `import openai`
client = openai.OpenAI() # reads OPENAI_API_KEY / OPENAI_BASE_URL
response = client.chat.completions.create(
model="Qwen3.5-397B-A17B",
messages=[{"role": "user", "content": "hello"}],
stream=True,
stream_options={"include_usage": True},
name="chat-response",
metadata={
"langfuse_session_id": "session-123",
"langfuse_user_id": "user-456",
"langfuse_tags": ["feature:chat"],
},
)⚠️ Nota: Use examples/.env.example e edite este ficheiro .env com as suas próprias variáveis de ambiente. Em seguida, carregue as variáveis de ambiente e configure o cliente Langfuse antes de importar o langfuse.openai.
Para implementações de referência completas e testadas deste padrão – cobrindo conversas multi-turn, identificação de sessões/utilizadores e Gestão de Prompts (Prompt Management) em quatro perfis de utilização diferentes (geração de código, FAQ, resumo de documentos e conteúdos longos), consulte:
- examples/simulate_dev_users.py
- examples/simulate_support_users.py
- examples/simulate_analyst_user.py
- examples/simulate_writer_user.py
⚠️ Atenção: Se consumir uma resposta em stream, não pare de iterar assim que finish_reason == "stop". Vários fornecedores compatíveis com a OpenAI (incluindo AI Endpoints) enviam o chunk com a contagem de tokens após esse ponto; uma interrupção antecipada significa que o Langfuse nunca o chega a processar, e o custo do trace aparecerá como zero, mesmo que os tokens tenham sido efetivamente consumidos.
Algumas adições transformam uma integração funcional numa integração verdadeiramente útil:

2. Monitorização de custos para a utilização do AI Endpoints
O Langfuse calcula o custo por trace a partir de uma tabela de preços de modelos e não inclui, de raiz, nenhuma entrada para modelos alojados na OVHcloud – cada trace apresentará um custo de $0 até que especifique quanto custa cada modelo. É para isso que serve a API de catálogo (https://catalog.endpoints.ai.ovh.net/rest/v1/models_v2) mencionada anteriormente: obtenha-a uma primeira vez, registe o preço de cada modelo com a API de modelos do Langfuse e o custo começará a ser calculado automaticamente para cada chamada subsequente.
import os
import requests
LANGFUSE_BASE_URL = os.environ["LANGFUSE_BASE_URL"]
LANGFUSE_PUBLIC_KEY = os.environ["LANGFUSE_PUBLIC_KEY"]
LANGFUSE_SECRET_KEY = os.environ["LANGFUSE_SECRET_KEY"]
catalog = requests.get(
"https://catalog.endpoints.ai.ovh.net/rest/v1/models_v2"
).json()
for model in catalog:
pricing = model.get("metadata", {}).get("usage_information", {}).get("pricing")
if not pricing:
continue
prices = {
{"million_input_tokens": "inputPrice", "million_output_tokens": "outputPrice"}[p["price_unit"]]: p["price"] / 1_000_000
for p in pricing
if p["price_unit"] in ("million_input_tokens", "million_output_tokens")
}
if not prices:
continue
requests.post(
f"{LANGFUSE_BASE_URL}/api/public/models",
auth=(LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY),
json={
"modelName": f"ovh-ai-endpoints/{model['id']}",
"matchPattern": f"(?i)^({model['name']})$",
"unit": "TOKENS",
**prices,
},
)Trata-se de uma chamada API única, não de um script mantido neste repositório: uma vez registado o preço de um modelo, este permanece no próprio Langfuse e qualquer cliente pode encontrá-lo lá. Consulte exemplos/README.md para obter a fundamentação completa, incluindo o motivo pelo qual os preços são apresentados em euros apesar de o Langfuse exibir o símbolo $.
Assim que existir uma definição de preço correspondente, o custo aparece imediatamente nos novos traces.
Numa chamada real ao Qwen3.5-397B-A17B:
- 28 tokens de entrada (0,60€ por milhão de tokens)
- 1430 tokens de saída (3,60€ por milhão de tokens)
O calculatedTotalCost do Langfuse resultou exatamente em: 28 * 0,0000006 + 1430 * 0,0000036 = 0,0051648€

Dashboard de custos do Langfuse
⚠️ Nota: O Langfuse apresenta sempre o custo com o prefixo $; não existe uma configuração de moeda por projeto. Se a tabela de preços que importar estiver em euros, como acontece com os preços do AI Endpoints da OVHcloud, cada valor na interface do Langfuse é um montante em euros exibido com um sinal de dólar.
Pode também verificar as diferentes métricas para a latência do modelo:

Dashboard de latência do Langfuse
Vá ainda mais longe e analise os traces que representam pedidos ou operações.

Exemplo de tracing do Langfuse
Parabéns! Já tem acesso a todas as funcionalidades do Langfuse num ambiente controlado.
Ir mais longe
- Alta Disponibilidade: esta arquitetura executa uma única réplica web e uma única réplica de trabalho, o que é suficiente para validar a configuração, mas não permite a remoção de um pod sem downtime. Aumente o número de réplicas em web para 2 ou mais numa implementação voltada para o público, e adicione um PodDisruptionBudget dimensionado em conformidade – um configurado para uma única réplica não protege nada.
- Dimensionamento automático: nem o HPA nem o KEDA estão ativados por predefinição; ative um deles se o volume de pedidos for imprevisível.
- Alertas de custos: a mesma API de modelos utilizada para carregar os preços pode ser consultada através da API de métricas do Langfuse para criar alertas de custos automatizados por equipa ou funcionalidade, em vez de verificar os dashboards manualmente.
- Network policies: nenhuma está definida nesta configuração; considere restringir o tráfego de saída (egress) do namespace do Langfuse apenas aos endpoints de serviços geridos de que este necessita.
Conclusão
Esta arquitetura proporciona-lhe uma implementação Langfuse que:
- Funciona no OVHcloud MKS sem bases de dados autogeridas – Postgres, Valkey, ClickHouse e Object Storage são serviços geridos pela OVHcloud
- Termina o TLS automaticamente através do Traefik e do cert-manager, renovando os certificados sem intervenção manual.
- Captura rastos completos, incluindo o consumo de tokens e a latência, de qualquer aplicação que chame o OVHcloud AI Endpoints através de uma alteração de apenas duas linhas no SDK
- Atribui custos por modelo, por utilizador e por sessão, utilizando o preçário do próprio catálogo do AI Endpoints em vez de suposições
- Mantém cada prompt, resposta e valor de custo dentro de uma infraestrutura controlada