Ir para o conteúdo

Como integrar com a API🔗

Controle de Acesso🔗

Para integrar sua aplição à API do Serpro LLM, procure-a na Loja do PLIN (ambiente de produção ou ambiente de experimentação) e solicite o acesso.

Após ter o acesso concedido, você conseguirá gerar um token no PLIN que será usado como chave de acesso para cada chamada que você fará à API do Serpro LLM através do cabeçalho Authorization: Bearer <TOKEN>:.

O Token expira

Lembre-se de que o token expira (geralmente em 1 hora). Quando isso acontece, ocorrerá erro HTTP 401 (Não autorizado). Sua aplicação deve ter alguma estratégia para renovação do token. Um exemplo de estratégia está no código que faz uso de um cliente http customizado no Langchain em Python.

Obtenção do Token🔗

Para gerar o Token, é necessário dispor de dois valores, um client_id e um client_secret, que são informados após ter o pedido aprovado na loja do PLIN. Guarde esses valores em um lugar seguro, pois garantem o acesso autorizado aos serviços do SerproLLM.

A seguir, códigos para obtenção do Token via curl, Python, Java e C#.NET:

curl -k -d "grant_type=client_credentials" \
        --user "CLIENT_ID:CLIENT_SECRET" \
        https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token
import requests 

data = {"grant_type":"client_credentials"}
url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token"
client_id = 'CLIENT_ID'
client_secret = 'CLIENT_SECRET'
result = requests.request('POST',url, data=data, auth=(client_id,client_secret))
String url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token";
HttpHeaders headers = new HttpHeaders();
String auth = "CLIENT_ID:CLIENT_SECRET";
String encodedAuth = Base64.getEncoder().encodeToString(auth.getBytes());
headers.set("Authorization", "Basic " + encodedAuth);

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("grant_type", "client_credentials");

HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(map, headers);

ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
string clientId = "CLIENT_ID"; 
string clientSecret = "CLIENT_SECRET"; 
string url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token";

using (HttpClient client = new HttpClient()) {
    var authToken = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{clientId}:{clientSecret}"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", authToken);

    var content = new FormUrlEncodedContent(new[] {
        new KeyValuePair<string, string>("grant_type", "client_credentials")
    });

    HttpResponseMessage response = await client.PostAsync(url, content);
    string responseString = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseString);
}

Erro de certificado

Caso ocorra erro de certificado, inclua a opção --cacert apontando para o arquivo ca-pro.pem. Para saber como baixar esse arquivo, acesse essa página.

curl -k -d "grant_type=client_credentials" \
    --header 'Content-Type:application/x-www-form-urlencoded'    
    --user "CLIENT_ID:CLIENT_SECRET" \
    https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token \
    --cacert /path/to/ca-pro.pem

Endpoints Open AI🔗

A API disponibilizada é compatível com o padrão Open AI, consistindo nos endpoints Listar Modelos, Completions e Chat Completions. Para ver uma descrição detalhada dos endpoints da API e seus conteúdos de entrada e saída, acesse a seção Especificação da API. O funcionamento básico dos endpoints da API são mostrados a seguir:

  • Listar Modelos: disponível em GET https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/models, lista os modelos disponíveis e seus recursos ofertados.

Exemplos de chamadas:

curl -X GET 'https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/models' \
    --header 'Authorization: Bearer TOKEN_OBTIDO_NO_PLIN'
import requests 

url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/models"-
headers = {"Authorization":f"Bearer {TOKEN_OBTIDO_NO_PLIN}"}
result = requests.request("GET",url,headers=headers)
if result.ok:
    res = result.json()
    lista_modelos = return res['data']
else:
    print("Nenhum modelo encontrado")
String url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/models"; 

HttpHeaders  headers = new HttpHeaders();
headers.set("Authorization", "Bearer TOKEN_OBTIDO_NO_PLIN");

HttpEntity<String> httpEntity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, httpEntity, String.class);
if (response.getStatusCode().is2xxSuccessful()) {
    String jsonResponse = responseString.getBody();
    System.out.println("response: " + jsonResponse);
} else {
    System.out.println("Request failed: " + response.getStatusCode());
}
string url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/models";

using (HttpClient client = new HttpClient()){
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    HttpResponseMessage response = await client.GetAsync(url);
    string responseString = await response.Content.ReadAsStringAsync();

    var? listModel = JsonSerializer.Deserialize<object>(responseString);
    if(listModel!=null && listModel.Data.Count()>0)
       Console.WriteLine("Modelo: "+listModel.Data[0].Id);
}
  • Completions: disponível em POST https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/completions, gera um texto à partir de um prompt único. Exemplo de chamada:
curl -X POST 'https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/completions' \
    --header 'Authorization: Bearer TOKEN_OBTIDO_NO_PLIN' \
    --header 'Content-Type: application/json' \
    --data-raw '{
        "model": "mistral-small-3.2-24b-instruct",
        "prompt": ["Diga oi!"],
        "temperature": 0,        
        "max_tokens": 10,
        "stream": false 
    }'
url = 'https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/completions'

headers = {"Authorization": f"Bearer TOKEN_OBTIDO_NO_PLIN"}
headers["Content-Type"]="application/json"

payload_data={"model": "mistral-small-3.2-24b-instruct"}
payload_data["prompt"] = ["Olá!"]
payload_data["temperature"] = 0
payload_data["max_tokens"] = 10
payload_data["stream"] = false
result = requests.request("POST", url, data=json.dumps(payload_data), headers=headers)
if result.ok:
    res = result.json()
    print(res)
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer TOKEN_OBTIDO_NO_PLIN");
headers.setContentType(MediaType.APPLICATION_JSON);
String url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/completions";

Map<String, Object> jsonData = new HashMap<>();
jsonData.put("model", "mistral-small-3.2-24b-instruct");
jsonData.put("prompt", new String[]{"Diga oi!"});
jsonData.put("temperature", 0.5);
jsonData.put("max_tokens", 100);
jsonData.put("stream", false);

ObjectMapper objectMapper = new ObjectMapper();
String jsonBody = objectMapper.writeValueAsString(jsonData);

HttpEntity<String> entity = new HttpEntity<>(jsonBody, headers);

ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
    String jsonResponse = response.getBody();
    System.out.println("Response: " + jsonResponse);
} else {
    System.out.println("Request failed: " + response.getStatusCode());
}
var payload = new  {
    model = "mistral-small-3.2-24b-instruct",
    prompt = new[] { "Bom dia" },
    temperature = 0,
    max_tokens = 10,
    stream = false
};

string jsonPayload = JsonSerializer.Serialize(payload);

string url = "https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/completions";

using (HttpClient client = new HttpClient()) {
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

    HttpContent content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

    HttpResponseMessage response = await client.PostAsync(url, content);
    string responseString = await response.Content.ReadAsStringAsync();
    Console.WriteLine(responseString);
}
  • Chat Completions: disponível em POST https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/chat/completions, gera uma resposta para uma conversa, podendo receber um histórico de mensagens anteriores. Exemplo de chamada no curl:
     curl -X POST 'https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1/chat/completions' \
        --header 'Authorization: Bearer TOKEN_OBTIDO_NO_PLIN' \
        --header 'Content-Type: application/json' \
        --data-raw '{
            "model": "mistral-small-3.2-24b-instruct",
            "messages": [{"role": "user","content": "Quem é melhor, Pelé ou Maradona?"}],
            "temperature": 0,        
            "max_tokens": 500,
            "stream": false       
        }'
    

Identificador do modelo

Chamadas aos endpoints de completion só funcionarão se no campo model for informado o identificador válido de um modelo existente no ambiente. Para conhecer os modelos disponíveis, acesse o endpoint de Listar Modelos.

Langchain🔗

É possível utilizar a biblioteca Langchain para se conectar na API do Serpro LLM, bastando para isso utilizar as interfaces da Open AI e informar o token obtido no PLIN no campo openai_api_key e a URL do serviço no PLIN no campo openai_api_base. Veja o exemplo abaixo em Python:

from langchain_openai import OpenAI

llm = OpenAI(    
    model="mistral-small-3.2-24b-instruct",
    openai_api_key="TOKEN OBTIDO NO PLIN",        
    openai_api_base="https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1",
    max_tokens=10,
    temperature=0,    
)

llm.invoke("Diga oi")

Langchain

Por tratar de um assunto relativamente novo, a biblioteca Langchain está em constante evolução e vem sempre apresentando novidades que ajudam a otimizar o código que faz uso de LLMs. Fique atento à versão mais recente e suas novidades.

OpenAI🔗

Também é possível utilizar os componentes da biblioteca OpenAI, que possui um cliente completo com as funcionalidades implementadas do padrão OpenAI. Um exemplo de uso da biblioteca OpenAI para consulta de modelos e posterior exibição de recursos de cada modelo é exibido a seguir:

from openai import OpenAI

client = OpenAI(
    base_url="https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1" ,
    api_key="TOKEN_OBTIDO_NO_PLIN"
)

models_response = client.models.list()
model_list = models_response.data

if model_list:
    print(f"A consulta devolveu **{len(model_list)}** modelo(s) disponibilizado(s) pelo SerproLLM:")
    for i, model_entry in enumerate(model_list):
        model_id = model_entry.id
        print(f"\nRecursos do modelo {model_id}:")
        if model_entry.features:
            print(f" * Tem raciocínio? {"Sim" if model_entry.features.get("has_reasoning",False) else "Não"}")
            print(f" * Processa conteúdo multimodal? {"Sim" if model_entry.features.get("is_multimodal",False) else "Não"}")
            print(f" * Possui suporte a tools? {"Sim" if model_entry.features.get("allow_tools",False) else "Não"}")
        else:
            print(f" * Modelo não possui recursos cadastrados.")
else:
    print("Não foram encontrados modelos.")

Problemas de SSL/Certificado do PLIN🔗

Os endereços seguros das APIs no PLIN são assinados através da cadeia de certificadores abaixo do ICP Brasil. Infelizmente, essa cadeia não costuma ser reconhecida como válida em muitos ambientes.

Para contornar o problema de forma definitiva, o ideal é fazer constar a cadeia na lista de certificadores válidos do ambiente desejado. O arquivo .pem da cadeia de certificados necessária pode ser baixado em: https://lcrspo.serpro.gov.br/ca/ca-pro.pem

No Python, você definir as variáveis de ambiente REQUESTS_CA_BUNDLE e SSL_CERT_FILE para que elas indiquem o caminho para o arquivo com a cadeia de certificados que você baixou. Exemplo:

import os
certificados_serpro = "/local/do/arquivo/ca-pro.pem"
os.environ["REQUESTS_CA_BUNDLE"] = certificados_serpro
os.environ["SSL_CERT_FILE"] = certificados_serpro

No Java, você pode adicionar o certificado no registro de certificados confiáveis keytool.

keytool -import -alias example -keystore $JAVA_HOME/jre/lib/security/cacerts -file /local/do/arquivo/ca-pro.pem

Outra opção é tentar desligar a verificação SSL das bibliotecas que cuidam do acesso ao endereço problemático, como no exemplo de código com cliente http customizado.

Problemas de Conexão Remota Desconectada🔗

Algumas bibliotecas clientes HTTP podem utilizar estratégias onde a conexão com o serviço é reusada por mais de uma requisição, sendo mantida aberta por mais tempo do que o suportado pelo servidor, levando a erros do tipo remote disconnected, ou seja, o servidor fecha a conexão com o cliente. É o caso da biblioteca urllib3 do Python.

Para resolver isso, é importante ter políticas de retry que permitam que seu código tente novamente fazer uma chamada caso ela apresente erros. No Langchain, certifique-se que o parâmetro max_retries não esteja com valor zero. É recomendado ainda criar um código que faça uso de um cliente http customizado.

Cliente HTTP Customizado🔗

Uma solução mais geral para vários dos problemas de conectividade é o uso de um cliente HTTP customizado que forneça algumas garantias. O exemplo abaixo, um cliente httpx é informado para a Langchain, resolvendo 3 problemas:

  • Vai evitar o pool de conexões do urllib3 que esbarra no erro de remote disconnected.
  • Vai ignorar a verificação SSL na conexão com o PLIN, evitando o erro do certificado.
  • Vai gerenciar a obtenção automática do token de acesso do PLIN sempre que ele expirar (status 401).

O código abaixo foi feito com a versão 0.1.3 da biblioteca langchain_openai e depende da existência das variáveis de ambiente PLIN_LLM_USERNAME e PLIN_LLM_PASSWORD contendo usuário e senha da sua aplicação no PLIN.

import httpx
import os
from langchain_openai import OpenAI

class PlinTokenAuth(httpx.Auth):   

    def __init__(self):
        # pega usuário e senha do plin de variáveis de ambiente
        self.username = os.environ["PLIN_LLM_USERNAME"]        
        self.password = os.environ["PLIN_LLM_PASSWORD"]

    def with_token(self, request, reset=False):
        if (reset or not hasattr(self,"token")):            
            self.token = httpx.post(
                url="https://api-serprollm.ni.estaleiro.serpro.gov.br/oauth2/token",   
                headers={"Content-Type": "application/x-www-form-urlencoded"}, 
                data="grant_type=client_credentials",
                auth=(self.username,self.password),
                verify=False
            ).json()["access_token"]
        request.headers['Authorization'] = f"Bearer {self.token}"
        return request

    def auth_flow(self, request):                        
        response = yield self.with_token(request)
        if response.status_code == 401:            
            yield self.with_token(request, reset=True)

llm = OpenAI(          
    openai_api_key="vazio", # o cliente customizado vai obter
    openai_api_base="https://api-serprollm.ni.estaleiro.serpro.gov.br/gateway/v1",
    model_name="mistral-small-3.2-24b-instruct",        
    max_retries=1,
    max_tokens=10,
    temperature=0,
    http_client=httpx.Client(auth=PlinTokenAuth(),verify=False)    
)

llm.invoke("Diga oi")