Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Commit

Permalink
instant_answer: use focus when a region is specified (#212)
Browse files Browse the repository at this point in the history
  • Loading branch information
remi-dupre authored Mar 19, 2021
1 parent e30aead commit 0fb52d2
Show file tree
Hide file tree
Showing 8 changed files with 18,172 additions and 28 deletions.
7 changes: 1 addition & 6 deletions idunn/api/geocoder.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import asyncio
import logging
from fastapi import Body, Depends
from shapely.geometry import Point
from ..geocoder.bragi_client import bragi_client
from ..geocoder.nlu_client import nlu_client, NluClientException
from ..geocoder.models import QueryParams, ExtraParams, IdunnAutocomplete
Expand Down Expand Up @@ -65,12 +64,8 @@ async def get_intentions():
if not query.nlu and not autocomplete_nlu_shadow_enabled:
return None

focus = None
if query.lon and query.lat:
focus = Point(query.lon, query.lat)

try:
return await nlu_client.get_intentions(text=query.q, lang=query.lang, focus=focus)
return await nlu_client.get_intentions(text=query.q, lang=query.lang)
except NluClientException as exp:
logger.info("Ignored NLU for '%s': %s", query.q, exp.reason(), extra=exp.extra)
return []
Expand Down
30 changes: 21 additions & 9 deletions idunn/api/instant_answer.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
from idunn import settings
from idunn.geocoder.nlu_client import nlu_client, NluClientException
from idunn.geocoder.bragi_client import bragi_client
from idunn.geocoder.models import QueryParams
from idunn.places import place_from_id, Place
from idunn.api.places_list import get_places_bbox_impl, PlacesQueryParam
from idunn.utils import maps_urls, result_filter
from idunn.utils.regions import get_region_lonlat
from idunn.instant_answer import normalize
from .constants import PoiSource
from .utils import Verbosity
Expand Down Expand Up @@ -71,14 +73,15 @@ class InstantAnswerResponse(BaseModel):
data: InstantAnswerData


def no_instant_answer(query=None, lang=None):
def no_instant_answer(query=None, lang=None, region=None):
if query is not None:
logger.info(
"get_instant_answer: no answer",
extra={
"request": {
"query": query,
"lang": lang,
"region": region,
}
},
)
Expand Down Expand Up @@ -182,7 +185,9 @@ async def get_instant_answer_intention(intention, query: str, lang: str):


async def get_instant_answer(
q: str = Query(..., title="Query string"), lang: str = Query("en", title="Language")
q: str = Query(..., title="Query string"),
lang: str = Query("en", title="Language"),
user_country: Optional[str] = Query(None, title="Region where the user is located"),
):
"""
Perform a query with result intended to be displayed as an instant answer
Expand All @@ -194,7 +199,13 @@ async def get_instant_answer(
normalized_query = normalize(q)

if len(normalized_query) > ia_max_query_length:
return no_instant_answer(query=q, lang=lang)
return no_instant_answer(query=q, lang=lang, region=user_country)

extra_geocoder_params = {}

if user_country and get_region_lonlat(user_country) is not None:
extra_geocoder_params["lon"], extra_geocoder_params["lat"] = get_region_lonlat(user_country)
extra_geocoder_params["zoom"] = 6

if normalized_query == "":
if settings["IA_SUCCESS_ON_GENERIC_QUERIES"]:
Expand All @@ -204,21 +215,22 @@ async def get_instant_answer(
maps_frame_url=maps_urls.get_default_url(no_ui=True),
)
return build_response(result, query=q, lang=lang)
return no_instant_answer(query=q, lang=lang)
return no_instant_answer(query=q, lang=lang, region=user_country)

if lang in nlu_allowed_languages:
try:
intentions = await nlu_client.get_intentions(text=normalized_query, lang=lang)
intentions = await nlu_client.get_intentions(
normalized_query, lang, extra_geocoder_params
)
if intentions:
return await get_instant_answer_intention(intentions[0], query=q, lang=lang)
except NluClientException:
# No intention could be interpreted from query
pass

# Direct geocoding query
bragi_response = await bragi_client.raw_autocomplete(
{"q": normalized_query, "lang": lang, "limit": 5}
)
query = QueryParams.build(q=normalized_query, lang=lang, limit=5, **extra_geocoder_params)
bragi_response = await bragi_client.autocomplete(query)
geocodings = (feature["properties"]["geocoding"] for feature in bragi_response["features"])
geocodings = sorted(
(
Expand All @@ -233,7 +245,7 @@ async def get_instant_answer(
)

if not geocodings:
return no_instant_answer(query=q, lang=lang)
return no_instant_answer(query=q, lang=lang, region=user_country)

place_id = geocodings[0][1]["id"]
result = await run_in_threadpool(get_instant_answer_single_place, place_id=place_id, lang=lang)
Expand Down
2 changes: 1 addition & 1 deletion idunn/geocoder/bragi_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class BragiClient:
def __init__(self):
self.client = httpx.AsyncClient(verify=settings["VERIFY_HTTPS"])

async def autocomplete(self, query: QueryParams, extra: ExtraParams):
async def autocomplete(self, query: QueryParams, extra: ExtraParams = ExtraParams()):
params = query.bragi_query_dict()
body = None
if extra.shape:
Expand Down
11 changes: 11 additions & 0 deletions idunn/geocoder/models/params.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ class QueryParams:
description="Perform NLU analysis to extract location and intention from the request.",
)

@classmethod
def build(cls, *args, **kwargs):
"""
Build an instance of `QueryParams` explicitly.
Dataclasses don't support `Query` annotations correctly through their
constructor, but we can initialize the object using the inner model
through this method.
"""
return cls(**cls.__pydantic_model__(*args, **kwargs).dict()) # pylint: disable = no-member

def bragi_query_dict(self):
"""
Return a dict with parameters accepted by the bragi API
Expand Down
36 changes: 24 additions & 12 deletions idunn/geocoder/nlu_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
import httpx
import logging
import re
from shapely.geometry import mapping
from unidecode import unidecode

from idunn import settings
from idunn.api.places_list import MAX_HEIGHT, MAX_WIDTH
from idunn.api.utils import Category
from idunn.utils import result_filter
from idunn.geocoder.models.params import QueryParams as GeocoderParams
from idunn import settings
from idunn.utils.circuit_breaker import IdunnCircuitBreaker
from idunn.utils import result_filter

from .models.geocodejson import Intention
from .bragi_client import bragi_client
Expand Down Expand Up @@ -114,12 +114,26 @@ def fuzzy_match(cls, query, bragi_res):
return True
return result_filter.check_bragi_response(q, bragi_res)

async def build_intention_category_place(self, cat_query, place_query, lang, is_brand=False):
async def build_intention_category_place(
self,
cat_query,
place_query,
lang,
is_brand=False,
extra_geocoder_params=None,
):
async def get_category():
return await self.classify_category(cat_query, is_brand=is_brand)

bragi_params = GeocoderParams.build(
q=place_query,
lang=lang,
limit=1,
**(extra_geocoder_params or {}),
)

bragi_result, category = await asyncio.gather(
bragi_client.raw_autocomplete(params={"q": place_query, "lang": lang, "limit": 1}),
bragi_client.autocomplete(bragi_params),
get_category(),
)

Expand Down Expand Up @@ -197,7 +211,7 @@ def build_place_query(cls, tags_list):

return " ".join(tags)

async def post_intentions(self, text, lang, _focus=None):
async def post_intentions(self, text, lang):
tagger_url = settings["NLU_TAGGER_URL"]
tagger_domain = settings["NLU_TAGGER_DOMAIN"]
# this settings is an immutable string required as a parameter for the NLU API
Expand All @@ -212,19 +226,17 @@ async def post_intentions(self, text, lang, _focus=None):
response_nlu.raise_for_status()
return response_nlu

async def get_intentions(self, text, lang, focus=None) -> [Intention]:
async def get_intentions(self, text, lang, extra_geocoder_params=None) -> [Intention]:
logs_extra = {
"intention_detection": {
"text": text,
"lang": lang,
"focus": mapping(focus).get("coordinates") if focus is not None else None,
"extra_geocoder_params": extra_geocoder_params,
}
}

try:
response_nlu = await tagger_circuit_breaker.call_async(
self.post_intentions, text, lang, focus
)
response_nlu = await tagger_circuit_breaker.call_async(self.post_intentions, text, lang)
except Exception:
logger.error("Request to NLU tagger failed", exc_info=True, extra=logs_extra)
return []
Expand Down Expand Up @@ -257,7 +269,7 @@ async def get_intentions(self, text, lang, focus=None) -> [Intention]:
# Brands are handled the same way categories except that we don't want to process
# them with the classifier.
intention = await self.build_intention_category_place(
cat_or_brand_query, place_query, lang=lang, is_brand=bool(brand_query)
cat_or_brand_query, place_query, lang, bool(brand_query), extra_geocoder_params
)
else:
# 1 category or brand
Expand Down
Loading

0 comments on commit 0fb52d2

Please sign in to comment.