Spaces:
Sleeping
Download docs/api_endpoints.md from AlleksDev/frimeet-api-nlp: direct link, hf CLI and curl.
- Browser
- Download file 35.5 kB
-
https://huggingface.co/spaces/AlleksDev/frimeet-api-nlp/resolve/main/docs/api_endpoints.md
- Command line
-
hf download hf://spaces/AlleksDev/frimeet-api-nlp/docs/api_endpoints.md
-
curl -L -o api_endpoints.md https://huggingface.co/spaces/AlleksDev/frimeet-api-nlp/resolve/main/docs/api_endpoints.md
API HTTP de Frimeet NLP
Documentacion del contrato HTTP publicado por el servicio NLP de Frimeet.
URLs base
Produccion en Hugging Face Spaces:
https://alleksdev-frimeet-api-nlp.hf.space
Desarrollo local:
http://localhost:8080
Documentacion interactiva:
- Swagger UI:
GET /docs - ReDoc:
GET /redoc - Especificacion OpenAPI:
GET /openapi.json
Todas las peticiones y respuestas con body utilizan application/json.
Resumen de endpoints
| Metodo | Ruta | Proposito | Autenticacion |
|---|---|---|---|
GET |
/ |
Informacion basica del servicio | No |
GET |
/health |
Comprobar que el proceso responde | No |
GET |
/ready |
Comprobar dependencias y contrato pgvector | No |
GET, POST |
/places/search/metrics |
Evaluar el buscador con el benchmark integrado | No |
POST |
/places/search |
Buscar lugares semanticamente | No |
POST |
/places/recommendations |
Recomendar lugares y redactar una respuesta | No |
POST |
/places/chat |
Conversacion orientada a lugares | No |
POST |
/internal/places/chat |
Interpretar chat y devolver candidatos tecnicos | Servicio |
POST |
/posts/recommendations |
Recomendar publicaciones | No |
POST |
/internal/posts/feed/rank |
Ordenar allowlist de posts | Servicio |
POST |
/internal/posts/clusters/runs |
Agendar entrenamiento de clusters | Servicio |
GET |
/internal/posts/clusters/status |
Estado del run activo | Servicio |
GET |
/internal/posts/clusters/runs/{id} |
Detalle de un run de clusters | Servicio |
POST |
/internal/posts/clusters/runs/{id}/activate |
Activar/rollback de run | Servicio |
POST |
/search |
Busqueda global sobre todos los recursos | Condicional |
POST |
/internal/search/candidates |
Candidatos tecnicos para la API principal | Servicio |
La autenticacion de /search solo es obligatoria cuando el body contiene
requester_id. En ese caso se envia:
Authorization: Bearer <SEARCH_INTERNAL_TOKEN>
El token debe ser enviado por la API principal, no directamente por la aplicacion movil.
1. Endpoints del sistema
GET /
Devuelve informacion basica y enlaces del servicio.
curl "https://alleksdev-frimeet-api-nlp.hf.space/"
Respuesta 200 OK:
{
"service": "frimeet-api-nlp",
"status": "ok",
"docs": "/docs",
"health": "/health",
"ready": "/ready"
}
GET /health
Indica que el proceso HTTP esta vivo. No comprueba PostgreSQL ni las funciones de pgvector.
Respuesta 200 OK:
{
"status": "ok"
}
GET /ready
Comprueba que el servicio puede utilizar su almacenamiento vectorial. Cuando
VECTOR_STORE_PROVIDER=aws_pgvector, valida:
- disponibilidad de la extension
vector; - existencia y permiso de ejecucion de
match_places(...); - existencia y permiso de ejecucion de
match_posts(...); - existencia y permiso de ejecucion de
search_resource_embeddings(...). - presencia de los filtros V2 (
event_active_at, umbrales y parseo temporal seguro).
Respuesta correcta: 200 OK con status: "ready".
Si una dependencia o funcion SQL no esta disponible, responde 503 Service Unavailable
con status: "not_ready" y el detalle en dependencies.vector_store.contract.
2. Lugares
Campos comunes de busqueda de lugares
POST /places/search y POST /places/recommendations utilizan el mismo body base.
| Campo | Tipo | Obligatorio | Default | Restricciones |
|---|---|---|---|---|
query |
string |
Si | - | 1 a 500 caracteres |
city |
string | null |
No | null |
Maximo 80 caracteres |
state |
string | null |
No | null |
Maximo 80 caracteres |
filters |
object |
No | {} |
Filtros adicionales |
limit |
integer |
No | 10 |
Entre 1 y 20 |
lat |
number | null |
No | null |
Entre -90 y 90 |
lng |
number | null |
No | null |
Entre -180 y 180 |
radius |
integer |
No | 5000 |
Entre 1 y 50000 metros |
lat y lng deben enviarse juntos. Si se omite cualquiera de los dos, la API responde
422 Unprocessable Entity.
Campos de filters:
| Campo | Tipo | Default |
|---|---|---|
city |
string | null |
null |
state |
string | null |
null |
category |
string | null |
null |
price_range |
string | null |
null |
is_active |
boolean | null |
true |
occasion |
string | null |
null |
required_attribute_states |
object | null |
null |
attribute_terms_any |
string[] | null |
null |
entertainment_features_any |
string[] | null |
null |
contained_items_any |
string[] | null |
null |
menu_items_any |
string[] | null |
null |
En el ranking conversacional, la ausencia de descripcion, tags o atributos opcionales
permanece neutral. required_attribute_states es un filtro duro solicitado
explicitamente: excluye tanto valores incompatibles como estados desconocidos. Los
arreglos *_any aceptan el lugar si coincide al menos una de sus senales indexadas.
Si city o state aparecen tanto en el nivel principal como dentro de filters, tiene
prioridad el valor del nivel principal.
POST /places/search
Realiza recuperacion semantica de lugares mediante FastText y pgvector. Llama a la API principal para obtener un allowlist de lugares cercanos cuando se incluyen coordenadas.
No utiliza Llama/Groq para elegir resultados.
Ejemplo:
curl -X POST "https://alleksdev-frimeet-api-nlp.hf.space/places/search" \
-H "Content-Type: application/json" \
-d '{
"query": "cafeteria tranquila para estudiar",
"city": "Tuxtla Gutierrez",
"filters": {
"category": "cafeteria",
"is_active": true
},
"limit": 5
}'
Ejemplo con filtro geografico:
{
"query": "un lugar tranquilo para cenar",
"lat": 16.7531,
"lng": -93.1156,
"radius": 10000,
"limit": 5
}
Respuesta 200 OK:
{
"query": "cafeteria tranquila para estudiar",
"places": [
{
"id": "place-id",
"name": "Nombre del lugar",
"score": 0.7312,
"category": "cafeteria",
"city": "Tuxtla Gutierrez",
"state": "Chiapas",
"metadata": {}
}
],
"metrics": {
"engine": "fasttext_mean_embeddings",
"candidate_retrieval": "pgvector",
"score_metric": "cosine_similarity",
"field_weights": {
"tags": 6,
"category": 4,
"description": 3,
"name": 1
},
"ranking_parameters": {
"dimension": 300.0
},
"relevance_threshold": 0.5,
"match_quality": "confident",
"query_token_count": 4,
"matched_query_token_count": 4,
"query_coverage": 1.0,
"scope": "request",
"ground_truth_available": false,
"candidate_count": 5,
"returned_count": 5,
"nonzero_score_count": 5,
"min_score": 0.42,
"max_score": 0.7312,
"mean_score": 0.57,
"location_filter_applied": false,
"nearby_place_count": null,
"radius_meters": null
}
}
match_quality puede ser confident, low_confidence o no_match.
POST /places/recommendations
Busca lugares con el mismo flujo de /places/search y despues utiliza Llama/Groq para
redactar un mensaje conversacional. El LLM solo recibe los lugares recuperados; no elige
lugares adicionales ni puede inventar IDs.
Body: igual al de POST /places/search.
curl -X POST "https://alleksdev-frimeet-api-nlp.hf.space/places/recommendations" \
-H "Content-Type: application/json" \
-d '{
"query": "quiero una cena romantica",
"city": "Tuxtla Gutierrez",
"limit": 5
}'
Respuesta 200 OK:
{
"query": "quiero una cena romantica",
"message": "Encontre algunas opciones que pueden interesarte.",
"places": [],
"metrics": {
"engine": "fasttext_mean_embeddings",
"candidate_retrieval": "pgvector",
"score_metric": "cosine_similarity",
"field_weights": {
"tags": 6,
"category": 4,
"description": 3,
"name": 1
},
"ranking_parameters": {
"dimension": 300.0
},
"relevance_threshold": 0.5,
"match_quality": "confident",
"query_token_count": 4,
"matched_query_token_count": 4,
"query_coverage": 1.0,
"scope": "request",
"ground_truth_available": false,
"candidate_count": 5,
"returned_count": 5,
"nonzero_score_count": 5,
"min_score": 0.42,
"max_score": 0.7312,
"mean_score": 0.57,
"location_filter_applied": false,
"nearby_place_count": null,
"radius_meters": null
},
"metadata": {
"strategy": "pgvector_candidates_plus_fasttext_mean_embeddings",
"ranking": "fasttext_mean_embeddings",
"response_mode": "confident",
"relevance_threshold": 0.5,
"llm_provider": "groq",
"llm_model": "llama-3.1-8b-instant",
"used_llm": true,
"guard_reason": null,
"places_used_as_context": [],
"timestamp": "2026-07-04T12:00:00+00:00"
}
}
metrics tiene el mismo esquema completo mostrado en /places/search.
POST /places/chat
Endpoint conversacional orientado a lugares.
Body:
| Campo | Tipo | Obligatorio | Default | Restricciones |
|---|---|---|---|---|
message |
string |
Si | - | 1 a 1000 caracteres |
city |
string | null |
No | null |
Maximo 80 caracteres |
state |
string | null |
No | null |
Maximo 80 caracteres |
filters |
object |
No | {} |
Mismo esquema de filtros de lugares |
limit |
integer |
No | 5 |
Entre 1 y 8 |
Ejemplo:
{
"message": "Quiero salir con amigos a un lugar con musica",
"city": "Tuxtla Gutierrez",
"limit": 5
}
Respuesta:
{
"response_id": "uuid-de-respuesta",
"nlp_trace_id": "uuid-de-traza",
"message": "隆Claro! Encontr茅 opciones que se acercan bastante a lo que buscas. Lo que m谩s las conecta con tu idea es m煤sica y planes para amigos; ojal谩 alguna se convierta en tu pr贸ximo plan.",
"places": [],
"metadata": {}
}
GET|POST /places/search/metrics
Ejecuta el benchmark offline integrado para evaluar la calidad del buscador de lugares. No evalua una consulta enviada por el usuario y no acepta body.
Query parameter:
| Parametro | Tipo | Default | Restricciones |
|---|---|---|---|
k |
integer |
5 |
Entre 1 y 20 |
Ambos metodos son equivalentes:
curl "https://alleksdev-frimeet-api-nlp.hf.space/places/search/metrics?k=5"
La respuesta incluye:
engine,benchmarkyqrels_source;precision_at_k,recall_at_k,mrr,mapyndcg_at_kagregados;- metricas individuales de cada consulta del benchmark;
- definiciones de las metricas;
recommended_metric, actualmente nDCG@k.
POST /internal/places/chat
Endpoint V2 consumido exclusivamente por la API principal. La app movil nunca debe llamarlo directamente.
Authorization: Bearer <NLP_SERVICE_TOKEN>
Content-Type: application/json
Request minimo:
{
"conversation_id": "cb3456ef-598e-49d9-9bf9-b2ba99055ad7",
"turn": 1,
"message": "recomiendame una cafeteria cerca del Parque Central",
"state": {},
"user_location": {"lat": 16.7531, "lng": -93.1156},
"candidate_limit": 30,
"result_limit": 5
}
La respuesta devuelve action, mensaje, state_patch, location_directive, IDs y
scores tecnicos. No devuelve cards, coordenadas ni metadata privada. clarification y
no_match siempre tienen candidates=[]. Go debe fusionar el patch de forma atomica,
hidratar los IDs, revalidar el anchor y aplicar distancia/PostGIS antes de responder a
la app.
Un turno puramente social se devuelve como action="no_match",
unresolved=["non_search_input"], candidates=[] y
metadata.input_kind="non_search", con un mensaje conversacional. Esto evita convertir
saludos como ola en categorias cercanas por similitud. Si el mensaje tambien contiene
una solicitud de lugares, se procesa como busqueda normal.
state puede incluir target_category, hard_filters, soft_preferences,
exclusions, reference, explicit_target_location y pending_clarification. Si
taxonomy_version no coincide con la version desplegada, el endpoint responde 409.
Para action="recommendations", message enlaza la solicitud con evidencia
verificada de los candidatos (matched_reasons, categoria, etiquetas, atributos,
entretenimiento, elementos contenidos o menu). No menciona nombres ni posiciones
porque Go hidrata y reordena las cards posteriormente. Cuando existe al menos un
candidato con evidencia suficiente, NLP no completa el limite con candidatos debiles;
si el usuario pidio una categoria explicita y ninguno tiene evidencia primaria
suficiente, responde no_match sin cards irrelevantes. En busquedas abiertas todavia
puede conservar sugerencias debiles y comunicar baja confianza.
message siempre es un unico texto plano listo para mostrarse. NLP rechaza respuestas
del modelo que incluyan alternativas, versiones, encabezados, listas, Markdown, JSON o
explicaciones sobre como fue redactado el mensaje y utiliza el fallback seguro.
Nombre, descripcion y menu tienen mayor peso lexical que las etiquetas. Para una categoria explicita, una categoria compatible solo se acepta si el nombre, descripcion, menu o contenido estructurado contiene evidencia especifica; un tag aislado puede ayudar a ordenar candidatos, pero no basta para devolver la card ni para justificar el mensaje.
Cuando action="clarification", la respuesta incluye entre 2 y 5 botones y persiste
la allowlist correspondiente en state_patch.pending_clarification:
{
"action": "clarification",
"message": "驴Como quieres usar Parque Central?",
"state_patch": {
"target_category": "cafe",
"pending_clarification": {
"id": "22222222-2222-4222-8222-222222222222",
"kind": "location_scope",
"options": [
{
"id": "target_results",
"value": "target_results",
"label": "Buscar cerca de Parque Central",
"place_id": null,
"attributes": []
},
{
"id": "reference_entity",
"value": "reference_entity",
"label": "Usarlo solo para identificar la referencia",
"place_id": null,
"attributes": []
}
],
"location_anchor_text": "parque central",
"radius_meters": null,
"strict_radius": false
}
},
"candidates": [],
"clarification": {
"id": "22222222-2222-4222-8222-222222222222",
"kind": "location_scope",
"prompt": "驴Como quieres usar Parque Central?",
"options": [
{
"id": "target_results",
"label": "Buscar cerca de Parque Central",
"message": "Buscar cerca de Parque Central"
},
{
"id": "reference_entity",
"label": "Usarlo solo para identificar la referencia",
"message": "Usarlo solo para identificar la referencia"
}
]
}
}
Para resolverla, Go reenvia el estado persistido y una seleccion estructurada:
{
"conversation_id": "cb3456ef-598e-49d9-9bf9-b2ba99055ad7",
"turn": 2,
"message": "Buscar cerca de Parque Central",
"clarification_choice": {
"clarification_id": "22222222-2222-4222-8222-222222222222",
"option_id": "target_results"
},
"state": {
"target_category": "cafe",
"pending_clarification": {
"id": "22222222-2222-4222-8222-222222222222",
"kind": "location_scope",
"options": [
{
"id": "target_results",
"value": "target_results",
"label": "Buscar cerca de Parque Central",
"place_id": null,
"attributes": []
},
{
"id": "reference_entity",
"value": "reference_entity",
"label": "Usarlo solo para identificar la referencia",
"place_id": null,
"attributes": []
}
],
"location_anchor_text": "parque central",
"radius_meters": null,
"strict_radius": false
},
"taxonomy_version": "places-taxonomy-v3"
},
"user_location": {"lat": 16.7531, "lng": -93.1156},
"candidate_limit": 30,
"result_limit": 5
}
NLP compara ambos IDs con la allowlist del estado, consume la aclaracion en el mismo
turno y devuelve state_patch.pending_clarification=null. Una seleccion inexistente,
obsoleta o repetida responde 409; no se interpreta por similitud con message.
Ejemplo de directiva sin ubicacion explicita:
{
"source": "user_current",
"scope": "user_current_location",
"anchor_place_id": null,
"anchor_text": null,
"radius_meters": null,
"strict_radius": false
}
Para cafeterias como la de Hello Kitty cerca del Parque Central, NLP no elige en
silencio entre usar el parque como zona de resultados o como ayuda para identificar la
referencia: devuelve action=clarification, unresolved=["location_scope"] y conserva
el contexto no ambiguo en state_patch.
No es obligatorio escribir una categoria literal. El parser combina defaults de alta
precision con un clasificador local de prototipos FastText: quiero comer algo se
interpreta como target_category=restaurant, y formulaciones semanticamente cercanas
pueden obtener la misma categoria aunque no coincidan palabra por palabra. El
clasificador exige similitud minima y separacion frente a la segunda categoria; una
frase abierta como quiero salir aun solicita aclaracion. Una categoria explicita tiene
prioridad y una nueva intencion clara elimina un pending_clarification obsoleto.
Las opciones de categoria conservan option.id y el value pendiente como valores
tecnicos. NLP usa el catalogo en espa帽ol de la API principal, humaniza la etiqueta como
fallback y solo publica hipotesis semanticas respaldadas por evidencia local. No expone
conteos preliminares de candidatos, porque la API principal puede aplicar filtros
adicionales. La API principal no debe alterar IDs, valores, orden ni allowlist.
Cuando hay coordenadas y proveedor nearby, un radio implicito comienza en
PLACES_CHAT_DEFAULT_RADIUS_METERS y puede ampliarse de forma acotada hasta
PLACES_CHAT_MAX_AUTO_RADIUS_METERS. El valor realmente consultado se devuelve en
location_directive.radius_meters. Un radio explicito (strict_radius=true) no se
amplia y los place_ids geograficos nunca se guardan en el estado conversacional.
Cambios de integracion fuera de NLP: docs/cambios_api_principal_chat_lugares.md y
docs/cambios_app_movil_chat_lugares.md.
3. Publicaciones
POST /posts/recommendations
Recupera publicaciones relacionadas semanticamente con una consulta.
Body:
| Campo | Tipo | Obligatorio | Default | Restricciones |
|---|---|---|---|---|
query |
string |
Si | - | 1 a 500 caracteres |
city |
string | null |
No | null |
Maximo 80 caracteres |
limit |
integer |
No | 10 |
Entre 1 y 20 |
Ejemplo:
curl -X POST "https://alleksdev-frimeet-api-nlp.hf.space/posts/recommendations" \
-H "Content-Type: application/json" \
-d '{
"query": "personas que quieran jugar futbol",
"city": "Tuxtla Gutierrez",
"limit": 10
}'
Respuesta:
{
"query": "personas que quieran jugar futbol",
"posts": [
{
"id": "post-id",
"title": "Partido de futbol este sabado",
"score": 0.69,
"city": "Tuxtla Gutierrez",
"tags": ["futbol", "deporte"],
"metadata": {}
}
],
"metadata": {}
}
POST /internal/posts/feed/rank
Endpoint servicio-a-servicio. Requiere:
Authorization: Bearer <NLP_SERVICE_TOKEN>
La API Go envia exclusivamente candidatos ya autorizados. NLP nunca introduce IDs externos y devuelve tambi茅n candidatos sin embedding con score t茅cnico de cold start.
{
"user_id": "uuid",
"candidate_posts": [
{
"post_id": "uuid",
"author_id": "uuid",
"created_at": "2026-07-05T12:00:00Z",
"social_affinity": 1.0,
"engagement_score": 0.4,
"author_affinity": 0.0
}
],
"snapshot_at": "2026-07-05T12:01:00Z",
"result_limit": 500
}
Respuesta:
{
"items": [
{
"post_id": "uuid",
"score": 0.87,
"cluster_id": 12
}
],
"ranking_version": "feed-v1",
"cluster_run_id": "uuid",
"cold_start": false,
"missing_embedding_count": 0,
"diversity_relaxations": 0,
"duplicate_penalized_count": 0
}
Operaci贸n interna de clusters
POST /internal/posts/clusters/runs
GET /internal/posts/clusters/status
GET /internal/posts/clusters/runs/{run_id}
POST /internal/posts/clusters/runs/{run_id}/activate
Authorization: Bearer <NLP_SERVICE_TOKEN>
POST /internal/posts/clusters/runs agenda el entrenamiento y responde 202 Accepted:
{
"status": "scheduled",
"message": "entrenamiento enviado al worker en background"
}
GET /internal/posts/clusters/runs/{run_id} devuelve estado, metricas y error si fallo.
El entrenamiento normal debe ejecutarse con python -m app.jobs.train_post_clusters;
los endpoints son controles administrativos. Si KMEANS_AUTO_ACTIVATE=false, primero
queda en validated y despues se activa con el endpoint de activate.
4. Busqueda global
POST /search
Busca en paralelo sobre:
places;posts;users;clubs;groups;events.
Calcula una sola representacion FastText de la consulta. En una busqueda normal,
places, posts, users, clubs, groups y events combinan similitud semantica
pgvector con full-text search de PostgreSQL mediante Reciprocal Rank Fusion (RRF).
Por eso un lugar puede aparecer por su nombre o por texto indexado aunque su similitud
FastText aislada sea baja. Para places con location.mode=strict se conserva
match_places(...), porque aplica el allowlist geografico directamente sobre el ID del
lugar y evita ampliar accidentalmente el radio solicitado.
Dentro del reranking de Places, nombre y descripcion concentran la mayor parte de la evidencia de campos; categoria y menu aportan respaldo adicional, mientras las tags se usan solo como una se帽al secundaria. Un campo ausente queda neutral y no resta puntos.
No recibe query parameters. Toda la entrada se envia en el body.
Body
| Campo | Tipo | Obligatorio | Default | Restricciones |
|---|---|---|---|---|
query |
string |
Si | - | 1 a 500 caracteres |
resource_types |
string[] | null |
No | Todos | Solo tipos enumerados |
per_type_limit |
integer |
No | 5 |
Entre 1 y 20 |
top_limit |
integer |
No | 10 |
Entre 1 y 50 |
requester_id |
UUID | null |
No | null |
Activa busqueda privada |
cursors |
object |
No | {} |
Cursor opaco por tipo de recurso |
location |
object | null |
No | null |
Ubicacion y radio del usuario |
filters |
object |
No | {} |
Filtros tipados por recurso |
No se aceptan campos adicionales.
En la primera solicitud se omite cursors. La respuesta incluye un bloque
pagination independiente para cada recurso consultado. Cuando has_more es true,
next_cursor se envia en la siguiente solicitud usando la misma consulta.
Ubicacion
location consulta una sola vez GET /api/v1/places/nearby en la API principal. Las
coordenadas siguen siendo propiedad de la API principal y no se almacenan en pgvector.
| Campo | Tipo | Obligatorio | Default | Restricciones |
|---|---|---|---|---|
lat |
number |
Si | - | Entre -90 y 90 |
lng |
number |
Si | - | Entre -180 y 180 |
radius |
integer |
No | 5000 |
Entre 1 y 50000 metros |
mode |
string |
No | prioritize |
prioritize o strict |
prioritize: aumenta solamente el ranking interno de lugares cercanos, sin modificar suscoresemantico.strict: excluye lugares, clubs presenciales y eventos cuyoplace_idno este dentro del radio.- La ubicacion aplica a
places,clubsyevents. No se inventa proximidad para usuarios, posts o grupos porque esos indices no contienen una relacion geografica verificada.
Filtros
Todos los campos son opcionales. Cada filtro se aplica solamente a los recursos que poseen esa se帽al.
| Campo | Recursos | Tipo | Descripcion |
|---|---|---|---|
city |
places, posts | string |
Coincidencia exacta sin distinguir mayusculas |
state |
places, posts | string |
Estado o region |
categories |
places, clubs | string[] |
Una o varias categorias admitidas |
price_ranges |
places | string[] |
Rangos como $, $$ o $$$ |
tags |
places, posts, events | string[] |
Debe coincidir al menos un tag |
published_from |
posts | datetime |
Fecha minima de publicacion ISO 8601 |
published_to |
posts | datetime |
Fecha maxima de publicacion ISO 8601 |
event_from |
events | datetime |
Inicio minimo del evento |
event_to |
events | datetime |
Inicio maximo del evento |
club_mode |
clubs | string |
online o in_person |
user_roles |
users | string[] |
Roles admitidos, por ejemplo cliente |
Ejemplo completo:
{
"query": "actividad para conocer personas",
"resource_types": ["places", "clubs", "events", "posts"],
"per_type_limit": 5,
"top_limit": 12,
"location": {
"lat": 16.7531,
"lng": -93.1156,
"radius": 10000,
"mode": "prioritize"
},
"filters": {
"city": "Tuxtla Gutierrez",
"categories": ["cafe", "community"],
"price_ranges": ["$", "$$"],
"tags": ["musica", "cultura"],
"published_from": "2026-07-01T00:00:00Z",
"event_from": "2026-07-04T00:00:00Z",
"event_to": "2026-08-04T23:59:59Z",
"club_mode": "in_person"
}
}
Busqueda publica
No requiere Authorization.
curl -X POST "https://alleksdev-frimeet-api-nlp.hf.space/search" \
-H "Content-Type: application/json" \
-d '{
"query": "ajedrez universitario",
"resource_types": ["clubs", "events", "users"],
"per_type_limit": 5,
"top_limit": 10
}'
Busqueda con contexto de usuario
Cuando se incluye requester_id, la API principal debe agregar el Bearer token interno:
curl -X POST "https://alleksdev-frimeet-api-nlp.hf.space/search" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $SEARCH_INTERNAL_TOKEN" \
-d '{
"query": "amigos de la universidad",
"resource_types": ["groups"],
"requester_id": "00000000-0000-0000-0000-000000000001",
"per_type_limit": 5,
"top_limit": 10
}'
Si falta el token, es incorrecto o no esta configurado en el servicio, responde
403 Forbidden.
requester_id nunca debe confiarse directamente desde la app movil. La API principal
debe obtenerlo de la sesion autenticada y construir la llamada interna.
Respuesta
{
"query": "ajedrez universitario",
"normalized_query": "ajedrez universitario",
"top_results": [
{
"id": "club-id",
"resource_type": "clubs",
"title": "Club de Ajedrez Universitario",
"subtitle": "ajedrez",
"score": 0.92,
"semantic_score": 0.81,
"lexical_score": 0.38,
"is_nearby": true,
"proximity_boost": 0.12,
"metadata": {}
}
],
"sections": {
"clubs": [
{
"id": "club-id",
"resource_type": "clubs",
"title": "Club de Ajedrez Universitario",
"subtitle": "ajedrez",
"score": 0.92,
"semantic_score": 0.81,
"lexical_score": 0.38,
"is_nearby": true,
"proximity_boost": 0.12,
"metadata": {}
}
],
"events": [],
"users": []
},
"pagination": {
"clubs": {
"page_size": 5,
"returned_count": 1,
"has_more": false,
"next_cursor": null
},
"events": {
"page_size": 5,
"returned_count": 0,
"has_more": false,
"next_cursor": null
},
"users": {
"page_size": 5,
"returned_count": 0,
"has_more": false,
"next_cursor": null
}
},
"metadata": {
"strategy": "parallel_hybrid_fasttext_full_text_rrf",
"queried_resources": ["clubs", "events", "users"],
"failed_resources": {},
"embedding_computed_once": true
}
}
top_results es una seleccion global diversificada. sections conserva los resultados
separados por tipo de recurso.
Solicitar la siguiente pagina
Cada recurso se pagina de manera independiente. Para cargar mas lugares, la interfaz
debe reutilizar exactamente query, declarar resource_types: ["places"] y enviar el
cursor recibido en pagination.places.next_cursor:
{
"query": "cafeteria tranquila",
"resource_types": ["places"],
"per_type_limit": 5,
"top_limit": 5,
"cursors": {
"places": "CURSOR_DEVUELTO_POR_LA_PAGINA_ANTERIOR"
}
}
La respuesta de cada seccion contiene:
| Campo | Descripcion |
|---|---|
page_size |
Limite solicitado para esa pagina |
returned_count |
Cantidad realmente devuelta |
has_more |
Indica si existe al menos otra pagina |
next_cursor |
Cursor opaco de continuacion o null si termino |
Reglas de los cursores:
- estan asociados al tipo de recurso, consulta, filtros, ubicacion, usuario y tamano de pagina;
- no deben interpretarse ni construirse en la app cliente;
- no pueden reutilizarse con otra consulta o con otro recurso;
- cuando se envia
cursors,resource_typeses obligatorio y debe contener sus claves; - se pueden pedir varias continuaciones en una llamada enviando un cursor por recurso.
El contrato SQL utiliza el ID externo como desempate estable cuando dos resultados tienen el mismo puntaje. Esto evita cambios arbitrarios de orden entre paginas consecutivas.
Si falla un proveedor individual, los demas pueden responder normalmente. El recurso
fallido aparece en metadata.failed_resources.
Privacidad
- Sin
requester_idsolo se consideran recursos activos y publicos. - Con
requester_id, un grupo privado puede aparecer si el usuario es creador, miembro agregado o invitado segunauthorized_user_ids. creator_idyauthorized_user_idsse utilizan internamente, pero se eliminan de la metadata enviada al cliente.
5. Errores comunes
| Estado | Significado |
|---|---|
403 |
Falta o es incorrecto el Bearer token de una busqueda con requester_id |
413 |
El body supera el limite configurado por MAX_REQUEST_BODY_BYTES |
422 |
Body, UUID, coordenadas, limites o query parameters invalidos |
503 |
PostgreSQL, pgvector o una funcion SQL requerida no esta disponible |
Ejemplo de error de validacion 422:
{
"detail": [
{
"type": "missing",
"loc": ["body", "query"],
"msg": "Field required"
}
]
}
6. Candidatos internos de busqueda
POST /internal/search/candidates
Endpoint de servicio consumido exclusivamente por la API principal. La app movil no debe llamarlo directamente.
Authorization: Bearer <NLP_SERVICE_TOKEN>
Content-Type: application/json
Ejemplo minimo:
{
"query": "club universitario de ajedrez",
"resource_types": ["clubs", "groups", "events", "posts"],
"candidate_limit_per_type": 50,
"top_limit": 100,
"as_of": "2026-07-12T14:00:00Z"
}
La respuesta contiene secciones independientes con id, resource_type, score,
semantic_score y lexical_score. No incluye metadata de UI ni registros hidratados.
La API principal aplica permisos y reglas de negocio, carga los recursos vigentes y
conserva el orden del ranking.
NLP aplica antes del LIMIT los umbrales configurados. Un candidato se acepta cuando
supera el umbral semantico o el lexical; si ninguno lo supera se descarta y no se
rellena la respuesta. Para eventos tambien exige que
start_time + duration_minutes > as_of, evitando calcular ranking sobre eventos ya
finalizados. Los cursores quedan ligados a query, recursos, filtros, ubicacion,
as_of y version de la politica de umbrales.
7. Variables relacionadas
| Variable | Uso |
|---|---|
SEARCH_INTERNAL_TOKEN |
Valida Authorization: Bearer ... cuando /search recibe requester_id |
NLP_SERVICE_TOKEN |
Autentica /internal/posts/*, /internal/search/candidates y /internal/places/chat; debe coincidir con Go |
MAIN_API_INTERNAL_TOKEN |
Autentica jobs y resolucion de anchors al consumir endpoints internos de Go |
MAIN_API_PLACE_ANCHOR_RESOLVE_PATH |
Ruta interna Go para resolver nombres de lugares usados como anchors o referencias |
PLACES_CHAT_V2_ENABLED |
Feature flag del chat interno; debe iniciar en false |
PLACES_CHAT_LLM_ENABLED |
Habilita solo la redaccion opcional; no cambia candidatos ni accion |
PLACES_CHAT_CANDIDATE_LIMIT |
Maximo tecnico de candidatos de contenido, entre 1 y 40 |
PLACES_CHAT_MIN_CONTENT_SCORE |
Umbral minimo antes de devolver candidatos |
PLACES_CHAT_INTENT_MIN_CONFIDENCE |
Confianza minima; por debajo se solicita aclaracion |
PLACES_CHAT_AMBIGUITY_DELTA |
Diferencia maxima para considerar ambiguos dos anchors |
PLACES_CHAT_DEFAULT_RADIUS_METERS |
Radio inicial para ubicacion implicita; default 5000 |
PLACES_CHAT_MAX_AUTO_RADIUS_METERS |
Limite de expansion automatica no estricta; default 50000 |
PLACES_CHAT_RANKING_VERSION |
Version observable de la politica de ranking; valor actual places-chat-v5 |
PLACES_CHAT_TAXONOMY_VERSION |
Version del parser y del estado conversacional; valor actual places-taxonomy-v3 |
MAX_REQUEST_BODY_BYTES |
Debe ser al menos 131072; valor recomendado 262144 para 500 candidatos |
REQUEST_TIMEOUT_SECONDS |
Presupuesto maximo para search interno y timeout de conexion/consulta pgvector |
RATE_LIMIT_REQUESTS_PER_WINDOW |
Limite local por IP/ruta para endpoints publicos |
INTERNAL_RATE_LIMIT_REQUESTS_PER_WINDOW |
Limite local por IP/ruta para endpoints internos |
RATE_LIMIT_WINDOW_SECONDS |
Ventana del limitador local; el gateway puede agregar un limite distribuido |
VECTOR_STORE_PROVIDER |
Selecciona aws_pgvector o el proveedor mock |
PGVECTOR_* |
Conexion y roles de PostgreSQL/pgvector |
EMBEDDING_PROVIDER |
Proveedor de embeddings; produccion utiliza fasttext |
EMBEDDING_DIMENSION |
Dimension vectorial; FastText utiliza 300 |
GROQ_API_KEY |
Habilita Groq/Llama para redactar respuestas conversacionales |
MAIN_API_BASE_URL |
API principal usada para fuentes y filtros geograficos |
MAIN_API_USERS_SNAPSHOT_PATH |
Snapshot interno paginado de usuarios para search |
MAIN_API_CLUBS_SNAPSHOT_PATH |
Snapshot interno paginado de clubs para search |
MAIN_API_GROUPS_SNAPSHOT_PATH |
Snapshot interno paginado de grupos para search |
MAIN_API_EVENTS_SNAPSHOT_PATH |
Snapshot interno paginado de eventos para search |
MAIN_API_PLACES_NEARBY_PATH |
Endpoint que resuelve los IDs dentro del radio solicitado |
GLOBAL_SEARCH_NEARBY_BOOST |
Peso de priorizacion geografica; default 0.12 |
PUBLIC_GLOBAL_SEARCH_ENABLED |
Mantiene o deshabilita el endpoint publico heredado POST /search |
GLOBAL_SEARCH_MIN_SEMANTIC_SCORE |
Umbral semantico global; default 0.30 |
GLOBAL_SEARCH_MIN_LEXICAL_SCORE |
Umbral lexical global; default 0.05 |
GLOBAL_SEARCH_RESOURCE_THRESHOLDS_JSON |
Overrides opcionales por recurso con semantic_min y lexical_min |
GLOBAL_SEARCH_THRESHOLD_POLICY_VERSION |
Version estable incluida en cursores para invalidarlos al cambiar la politica |
top_limit se conserva en el request y en la huella del cursor para mantener el
contexto coordinado con Go. NLP devuelve candidatos por seccion; la composicion final de
top_results pertenece a la API principal.