Callsistdocs
Guías

Análisis con LLM

Compatible con la forma de OpenAI. Lo que cambia es qué campos se atienden.

POST /v1/chat/completions. Scope: llm:invoke.

La forma de petición y respuesta es la de OpenAI, así que el SDK oficial funciona apuntando base_url a https://api.callsist.com/v1. Lo que cambia es qué campos se atienden.

Petición

{
  "model": "callsist-llm-1-small",     // o "callsist-llm-1-large"
  "messages": [
    { "role": "system", "content": "Eres un analista de calidad de un BPO." },
    { "role": "user", "content": "…" }
  ],
  "temperature": 0,                     // 0 a 2
  "max_tokens": 4000,                   // entero positivo, máximo 32000
  "stream": false,
  "response_format": { "type": "json_object" },
  "reasoning_effort": "none"            // "none" | "low" | "medium" | "high"
}

role solo admite system, user y assistant. content solo admite cadena: el formato multimodal de OpenAI —array de partes— se rechaza con 400.

Todo campo que no esté en esa lista se descarta en silencio. No da error: se ignora. Eso incluye tools, tool_choice, top_p, seed, stop, n, presence_penalty, frequency_penalty y logprobs.

No hay tool calling

tools y tool_choice no existen en el esquema y se descartan sin avisar. Un mensaje con role: "tool" se rechaza con 400.

Esto rompe los frameworks de agentes que dan el tool calling por hecho: el modelo contesta en prosa, el framework no encuentra la llamada a herramienta que espera y falla con un mensaje que no apunta a la causa.

Para un RAG, el patrón correcto es recuperar antes y meter el contexto en el prompt:

  1. embebe la consulta con POST /v1/embeddings;
  2. busca en tu propio almacén de vectores;
  3. mete los fragmentos recuperados en el mensaje user o system;
  4. llama a /v1/chat/completions con response_format: { "type": "json_object" }.

Es lo mismo que haría el tool calling con una vuelta menos.

reasoning_effort — el parámetro que hay que entender

Los modelos de chat razonan por defecto y emiten unos 420 tokens de razonamiento oculto por llamada, aunque devuelvan JSON limpio. Callsist manda reasoning_effort: "none" automáticamente salvo que pidas otra cosa, y eso es lo correcto para clasificación y extracción: dejarlo activo duplica el coste del módulo y multiplica por 35 los tokens de salida.

"low" no ahorra: gasta lo mismo que el defecto. Si quieres razonamiento, pide "medium" o "high" a sabiendas; si no, no mandes el campo.

Respuesta

{
  "id": "chatcmpl_9xKp2mQvRt4L",
  "object": "chat.completion",
  "model": "callsist-llm-1-small",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "{\"resultado\":…}" }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 24318, "completion_tokens": 892, "total_tokens": 25210 }
}

finish_reason es "stop" o "length". Con "length" el JSON viene truncado: compruébalo antes de parsear, o tendrás un error de sintaxis que parece otra cosa.

JSON estructurado

response_format: { "type": "json_object" } empuja al modelo a devolver JSON, pero no lo garantiza a nivel de gramática. En producción:

  • pide el esquema exacto en el prompt, campo por campo;
  • temperature: 0;
  • valida el resultado contra tu esquema y reintenta una vez si no valida;
  • acepta que un campo opcional venga con otra forma sin tirar el resto de la respuesta.

Streaming

Con "stream": true la respuesta es SSE con trozos chat.completion.chunk y termina con data: [DONE]. Tres cosas que hay que saber:

  • El stream nunca trae usage. No hace falta mandar stream_options (se descarta): Callsist ya lo pide por dentro y la llamada sí se factura. Pero el bloque de uso no llega al cliente, así que si necesitas contar tokens, no uses streaming.
  • Un fallo a mitad de stream se cierra con [DONE], no con un error HTTP, porque la cabecera ya salió. Un cliente que solo mire [DONE] no distingue una respuesta completa de una cortada: comprueba que llegó un trozo con finish_reason.
  • Nunca mandes Idempotency-Key con stream: true. El middleware de idempotencia consume la respuesta entera para memorizarla: el streaming deja de serlo.

Para análisis por lotes, no uses streaming. No aporta nada, no da tokens y complica el manejo de errores.

Timeout

El cliente HTTP interno tiene 120 s y hasta 3 reintentos con espera creciente. En el peor caso una petición tarda varios minutos en devolver un error, y no hay forma de acotarlo desde la petición. Pon tu propio timeout —60 a 120 s para lotes— y trátalo como reintentable.

On this page