2022-02-16 11:10:59 +01:00
|
|
|
from typing import Dict, Any
|
|
|
|
|
2020-04-15 14:04:30 +02:00
|
|
|
import logging
|
2020-07-07 12:28:41 +02:00
|
|
|
import time
|
2021-12-06 18:55:39 +01:00
|
|
|
import json
|
2022-01-26 18:20:44 +01:00
|
|
|
from numpy import ndarray
|
2020-04-15 14:04:30 +02:00
|
|
|
|
2022-04-12 16:41:05 +02:00
|
|
|
from pydantic import BaseConfig
|
|
|
|
from fastapi import FastAPI, APIRouter
|
2021-11-19 11:34:32 +01:00
|
|
|
import haystack
|
2022-04-12 16:41:05 +02:00
|
|
|
from haystack import Pipeline
|
2022-03-21 11:58:51 +01:00
|
|
|
from haystack.telemetry import send_event_if_public_demo
|
2022-04-12 16:41:05 +02:00
|
|
|
|
|
|
|
from rest_api.utils import get_app, get_pipelines
|
|
|
|
from rest_api.config import LOG_LEVEL
|
2021-10-04 11:21:00 +02:00
|
|
|
from rest_api.schema import QueryRequest, QueryResponse
|
2020-04-15 14:04:30 +02:00
|
|
|
|
2021-10-04 11:21:00 +02:00
|
|
|
|
2021-04-07 17:53:32 +02:00
|
|
|
logging.getLogger("haystack").setLevel(LOG_LEVEL)
|
|
|
|
logger = logging.getLogger("haystack")
|
2020-11-04 09:54:02 +01:00
|
|
|
|
2021-10-13 14:23:23 +02:00
|
|
|
|
|
|
|
BaseConfig.arbitrary_types_allowed = True
|
2021-04-07 17:53:32 +02:00
|
|
|
|
2021-10-04 21:18:23 +02:00
|
|
|
|
2022-04-12 16:41:05 +02:00
|
|
|
router = APIRouter()
|
|
|
|
app: FastAPI = get_app()
|
|
|
|
query_pipeline: Pipeline = get_pipelines().get("query_pipeline", None)
|
|
|
|
concurrency_limiter = get_pipelines().get("concurrency_limiter", None)
|
2021-04-07 17:53:32 +02:00
|
|
|
|
|
|
|
|
2021-09-27 16:40:25 +02:00
|
|
|
@router.get("/initialized")
|
2021-11-11 09:40:58 +01:00
|
|
|
def check_status():
|
2021-09-27 16:40:25 +02:00
|
|
|
"""
|
2022-02-03 13:43:18 +01:00
|
|
|
This endpoint can be used during startup to understand if the
|
2021-09-27 16:40:25 +02:00
|
|
|
server is ready to take any requests, or is still loading.
|
|
|
|
|
|
|
|
The recommended approach is to call this endpoint with a short timeout,
|
|
|
|
like 500ms, and in case of no reply, consider the server busy.
|
|
|
|
"""
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2021-11-19 11:34:32 +01:00
|
|
|
@router.get("/hs_version")
|
|
|
|
def haystack_version():
|
2022-01-27 13:06:01 +01:00
|
|
|
"""
|
|
|
|
Get the running Haystack version.
|
|
|
|
"""
|
2021-11-19 11:34:32 +01:00
|
|
|
return {"hs_version": haystack.__version__}
|
|
|
|
|
|
|
|
|
2021-11-11 09:40:58 +01:00
|
|
|
@router.post("/query", response_model=QueryResponse, response_model_exclude_none=True)
|
2021-10-04 11:21:00 +02:00
|
|
|
def query(request: QueryRequest):
|
2022-01-27 13:06:01 +01:00
|
|
|
"""
|
2022-02-03 13:43:18 +01:00
|
|
|
This endpoint receives the question as a string and allows the requester to set
|
2022-01-27 13:06:01 +01:00
|
|
|
additional parameters that will be passed on to the Haystack pipeline.
|
|
|
|
"""
|
2021-04-07 17:53:32 +02:00
|
|
|
with concurrency_limiter.run():
|
2022-04-12 16:41:05 +02:00
|
|
|
result = _process_request(query_pipeline, request)
|
2021-04-07 17:53:32 +02:00
|
|
|
return result
|
|
|
|
|
|
|
|
|
2022-03-21 11:58:51 +01:00
|
|
|
@send_event_if_public_demo
|
2022-02-16 11:10:59 +01:00
|
|
|
def _process_request(pipeline, request) -> Dict[str, Any]:
|
2021-04-07 17:53:32 +02:00
|
|
|
start_time = time.time()
|
2022-02-03 13:43:18 +01:00
|
|
|
|
2021-09-10 11:41:16 +02:00
|
|
|
params = request.params or {}
|
2021-11-18 18:13:03 +01:00
|
|
|
|
|
|
|
# format global, top-level filters (e.g. "params": {"filters": {"name": ["some"]}})
|
|
|
|
if "filters" in params.keys():
|
|
|
|
params["filters"] = _format_filters(params["filters"])
|
|
|
|
|
|
|
|
# format targeted node filters (e.g. "params": {"Retriever": {"filters": {"value"}}})
|
2022-02-16 11:10:59 +01:00
|
|
|
for key in params.keys():
|
2021-11-18 18:13:03 +01:00
|
|
|
if "filters" in params[key].keys():
|
|
|
|
params[key]["filters"] = _format_filters(params[key]["filters"])
|
|
|
|
|
2022-02-03 13:43:18 +01:00
|
|
|
result = pipeline.run(query=request.query, params=params, debug=request.debug)
|
|
|
|
|
2022-02-16 11:10:59 +01:00
|
|
|
# Ensure answers and documents exist, even if they're empty lists
|
|
|
|
if not "documents" in result:
|
|
|
|
result["documents"] = []
|
|
|
|
if not "answers" in result:
|
|
|
|
result["answers"] = []
|
|
|
|
|
2022-01-26 18:20:44 +01:00
|
|
|
# if any of the documents contains an embedding as an ndarray the latter needs to be converted to list of float
|
2022-02-16 11:10:59 +01:00
|
|
|
for document in result["documents"]:
|
2022-01-26 18:20:44 +01:00
|
|
|
if isinstance(document.embedding, ndarray):
|
|
|
|
document.embedding = document.embedding.tolist()
|
2022-02-03 13:43:18 +01:00
|
|
|
|
|
|
|
logger.info(
|
2022-02-16 11:10:59 +01:00
|
|
|
json.dumps({"request": request, "response": result, "time": f"{(time.time() - start_time):.2f}"}, default=str)
|
2022-02-03 13:43:18 +01:00
|
|
|
)
|
2021-04-07 17:53:32 +02:00
|
|
|
return result
|
2021-11-18 18:13:03 +01:00
|
|
|
|
|
|
|
|
|
|
|
def _format_filters(filters):
|
|
|
|
"""
|
|
|
|
Adjust filters to compliant format:
|
|
|
|
Put filter values into a list and remove filters with null value.
|
|
|
|
"""
|
|
|
|
new_filters = {}
|
2021-11-22 09:36:14 +01:00
|
|
|
if filters is None:
|
2022-02-03 13:43:18 +01:00
|
|
|
logger.warning(
|
|
|
|
f"Request with deprecated filter format ('\"filters\": null'). "
|
|
|
|
f"Remove empty filters from params to be compliant with future versions"
|
|
|
|
)
|
2021-11-22 09:36:14 +01:00
|
|
|
else:
|
|
|
|
for key, values in filters.items():
|
|
|
|
if values is None:
|
2022-02-03 13:43:18 +01:00
|
|
|
logger.warning(
|
|
|
|
f"Request with deprecated filter format ('{key}: null'). "
|
|
|
|
f"Remove null values from filters to be compliant with future versions"
|
|
|
|
)
|
2021-11-22 09:36:14 +01:00
|
|
|
continue
|
2022-02-21 20:16:14 +01:00
|
|
|
|
|
|
|
if not isinstance(values, list):
|
2022-02-03 13:43:18 +01:00
|
|
|
logger.warning(
|
|
|
|
f"Request with deprecated filter format ('{key}': {values}). "
|
|
|
|
f"Change to '{key}':[{values}]' to be compliant with future versions"
|
|
|
|
)
|
2021-11-22 09:36:14 +01:00
|
|
|
values = [values]
|
|
|
|
|
|
|
|
new_filters[key] = values
|
2021-11-18 18:13:03 +01:00
|
|
|
return new_filters
|