Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/davidmpizarro/QuipuEco-Hackaton/llms.txt

Use this file to discover all available pages before exploring further.

POST /chat powers the QuipuEco voice agent — a conversational assistant that answers recycling questions in natural Spanish and, crucially, can drive the frontend to open the map or the impact dashboard on the user’s behalf. Unlike a generic chatbot, every request carries the current waste classification result as structured context, so the model always knows exactly what material the user is holding. The backend passes this context alongside the conversation history and an optional geolocation to Gemini gemini-3.1-flash-lite, which returns both a spoken response and an optional structured action instruction.

Request

Endpoint: POST http://localhost:8000/chat Content-Type: application/json

Body Parameters

mensaje
string
required
The user’s transcribed message text, captured by the Web Speech API in AgenteVoz.jsx. Must be a non-empty string. Example: "¿Dónde puedo reciclar esta botella?".
historial
array
The last up to 10 conversation turns. Sent as historial.slice(-10) in the frontend to bound request size. Each entry is an object with two fields:Omit or send an empty array [] when starting a fresh conversation.
contexto_residuo
object | null
The full classification result object returned by POST /clasificar, or null if the user has not yet scanned any item. When present, the agent uses this object to answer material-specific questions without the user needing to re-state what they are holding. The frontend always sends the most recent classification result:
contexto_residuo: resultado ?? null
lat
number
User latitude in decimal degrees, obtained from the browser’s navigator.geolocation API. Optional — if omitted, the agent will not be able to calculate distances or name the nearest collection point. Example: -12.0445.
lng
number
User longitude in decimal degrees. Optional — sent together with lat. Example: -76.9678.
Geolocation coordinates enable the backend’s Haversine distance algorithm to calculate how far the user is from each collection point in Lima and name the closest Tambo or Centro Verde in the response. Without lat/lng, the agent can still answer general recycling questions but will not provide distance-specific information such as “El Tambo Alondras está a 300 metros.”

Code Examples

const { data } = await axios.post(`${API_URL}/chat`, {
  mensaje: texto,
  historial: historial.slice(-10),
  contexto_residuo: resultado ?? null,
  ...(coordsRef.current ?? {}),
});

Example Request Body

{
  "mensaje": "¿Dónde puedo reciclar esta botella?",
  "historial": [
    {
      "rol": "agente",
      "texto": "He clasificado una botella de plástico PET."
    }
  ],
  "contexto_residuo": {
    "nombre": "Botella de plástico PET",
    "tipo": "plastico",
    "co2_evitado_kg": 0.18,
    "peso_estimado_kg": 0.03,
    "instrucciones": "Retira la etiqueta y enjuaga con agua fría.",
    "punto_acopio": "Tiendas Tambo",
    "consejo": "El plástico PET puede reciclarse hasta 10 veces.",
    "respuesta_voz": "He clasificado una botella de plástico PET."
  },
  "lat": -12.0445,
  "lng": -76.9678
}

Response

Content-Type: application/json A successful turn returns HTTP 200 with the following JSON object:

Response Fields

respuesta
string
required
The agent’s text response, spoken aloud by the browser’s SpeechSynthesis API immediately after the request completes. Written in natural Spanish optimised for TTS playback at rate: 0.95. Example: "El Tambo Alondras está a 300 metros de aquí, en la Av. Las Alondras 297, Santa Anita.".
accion
string | null
required
An optional navigation instruction for the frontend. When the agent determines that the user’s request is best served by switching views, it sets this field to one of two string values:
accion_data
string | null
The material category string used to filter the map when accion === "mapa". Matches the canonical tipo values from /clasificar: "plastico", "papel", "metal", "vidrio", "organico", "electronico", "peligroso", or "no_reciclable". null when accion is not "mapa".Example: "plastico".
punto_cercano
string | null
When accion === "mapa", the name of the specific collection point the agent identified as the closest or most relevant. App.jsx uses this alongside accion_data: if puntoCercano is set and accion_data matches the current classification type, punto_cercano is forwarded to VistaMapaPuntos as the puntoDestino prop instead of the category string. null when no specific point was identified or when accion is not "mapa".Example: "Tambo Alondras - Santa Anita".

Example Responses

{
  "respuesta": "El Tambo Alondras está a 300 metros de aquí, en la Av. Las Alondras 297, Santa Anita. Te abro el mapa para que puedas ir directo.",
  "accion": "mapa",
  "accion_data": "plastico",
  "punto_cercano": "Tambo Alondras - Santa Anita"
}

How Actions Are Handled in the Frontend

AgenteVoz.jsx processes the response in enviarMensaje(). The respuesta text is always spoken via TTS first. If an accion is present, the component stores a pending action in state and dispatches it after a 1.8-second delay — enough time for the agent’s voice response to begin playing before the view transitions:
// AgenteVoz.jsx — action dispatch logic
if (data.accion === "mapa") {
  setAccionPendiente({
    accion: "mapa",
    data: data.accion_data || null,
    puntoCercano: data.punto_cercano || null,
  });
} else if (data.accion === "dashboard") {
  setAccionPendiente({ accion: "dashboard", data: null, puntoCercano: null });
}
// AgenteVoz.jsx — deferred dispatch via useEffect
useEffect(() => {
  if (!accionPendiente) return;
  const timer = setTimeout(() => {
    onAccion(accionPendiente.accion, accionPendiente.data, accionPendiente.puntoCercano);
    setAccionPendiente(null);
  }, 1800);
  return () => clearTimeout(timer);
}, [accionPendiente]);
The onAccion callback is defined in App.jsx and routes to the correct view:
  • accion === "mapa" → Calls handleVerMapa. When punto_cercano is set and accion_data matches the current classification’s tipo, punto_cercano (a string) is passed as the destino argument; otherwise accion_data is used. VistaMapaPuntos receives the result as its puntoDestino prop: a string value is treated as a category filter for the collection-point list.
  • accion === "dashboard" → Switches the app to the ImpactDashboard view.

Conversation Examples

The following table shows how accion and accion_data vary with different user intents:
User messageaccionaccion_dataNotes
"¿Dónde puedo reciclar esta botella?""mapa""plastico"Requires contexto_residuo.tipo === "plastico"
"Muéstrame los puntos de electrónicos""mapa""electronico"Explicit category override
"Muéstrame mi impacto""dashboard"nullStats view trigger
"¿Cómo preparo el cartón?"nullnullInstructional — text/voice only
"¿Cuál es el Tambo más cercano?""mapa""plastico"With lat/lng → sets punto_cercano
"¿Qué es el plástico PET?"nullnullGeneral knowledge — no navigation
For the best agent experience, always send contexto_residuo after a successful /clasificar call. Without it, the agent must infer the material type from the conversation text alone, which is less reliable than having the structured classification data available.

Error Responses

422 Unprocessable Entity
object
Returned when mensaje is missing or historial entries are malformed. The detail array follows FastAPI’s standard validation format.
500 Internal Server Error
object
Returned when the Gemini API call fails. The frontend catches this silently and speaks a fallback message to the user:
const msg = "Lo siento, hubo un error al conectar. Intenta de nuevo.";
agregarMensaje("agente", msg);
hablar(msg);

Build docs developers (and LLMs) love