Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions libraries/botbuilder-ai/botbuilder/ai/qna/models/query_results.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,29 @@
# Licensed under the MIT License.

from typing import List

from msrest.serialization import Model
from .query_result import QueryResult


class QueryResults:
class QueryResults(Model):
""" Contains answers for a user query. """

def __init__(self, answers: List[QueryResult]):
_attribute_map = {
"answers": {"key": "answers", "type": "[QueryResult]"},
"active_learning_enabled": {"key": "activeLearningEnabled", "type": "bool"},
}

def __init__(
self, answers: List[QueryResult], active_learning_enabled: bool = None, **kwargs
):
"""
Parameters:
-----------

answers: The answers for a user query.

active_learning_enabled: The active learning enable flag.
"""
super(QueryResults, self).__init__(**kwargs)
self.answers = answers
self.active_learning_enabled = active_learning_enabled
30 changes: 27 additions & 3 deletions libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from botbuilder.schema import Activity
from botbuilder.core import BotTelemetryClient, NullTelemetryClient, TurnContext

from .models import FeedbackRecord, QueryResult
from .models import FeedbackRecord, QueryResult, QueryResults
from .utils import (
ActiveLearningUtils,
GenerateAnswerUtils,
Expand Down Expand Up @@ -86,6 +86,30 @@ async def get_answers(
------
List[QueryResult]
"""
result = await self.get_answers_raw(
context, options, telemetry_properties, telemetry_metrics
)

return result.answers

async def get_answers_raw(
self,
context: TurnContext,
options: QnAMakerOptions = None,
telemetry_properties: Dict[str, str] = None,
telemetry_metrics: Dict[str, int] = None,
) -> QueryResults:
"""
Generates raw answers from the knowledge base.

return:
-------
A list of answers for the user's query, sorted in decreasing order of ranking score.

rtype:
------
QueryResults
"""
if not context:
raise TypeError("QnAMaker.get_answers(): context cannot be None.")

Expand All @@ -94,10 +118,10 @@ async def get_answers(
"QnAMaker.get_answers(): TurnContext's activity must be an Activity instance."
)

result = await self._generate_answer_helper.get_answers(context, options)
result = await self._generate_answer_helper.get_answers_raw(context, options)

await self.on_qna_result(
result, context, telemetry_properties, telemetry_metrics
result.answers, context, telemetry_properties, telemetry_metrics
)

return result
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,12 @@

from ..qnamaker_endpoint import QnAMakerEndpoint
from ..qnamaker_options import QnAMakerOptions
from ..models import GenerateAnswerRequestBody, QnAMakerTraceInfo, QueryResult
from ..models import (
GenerateAnswerRequestBody,
QnAMakerTraceInfo,
QueryResult,
QueryResults,
)

QNAMAKER_TRACE_NAME = "QnAMaker"
QNAMAKER_TRACE_LABEL = "QnAMaker Trace"
Expand Down Expand Up @@ -58,6 +63,13 @@ def __init__(
async def get_answers(
self, context: TurnContext, options: QnAMakerOptions = None
) -> List[QueryResult]:
result: QueryResults = await self.get_answers_raw(context, options)

return result

async def get_answers_raw(
self, context: TurnContext, options: QnAMakerOptions = None
) -> QueryResults:
if not isinstance(context, TurnContext):
raise TypeError(
"GenerateAnswerUtils.get_answers(): context must be an instance of TurnContext"
Expand All @@ -66,11 +78,9 @@ async def get_answers(
hydrated_options = self._hydrate_options(options)
self._validate_options(hydrated_options)

result: List[QueryResult] = await self._query_qna_service(
context, hydrated_options
)
result: QueryResults = await self._query_qna_service(context, hydrated_options)

await self._emit_trace_info(context, result, hydrated_options)
await self._emit_trace_info(context, result.answers, hydrated_options)

return result

Expand Down Expand Up @@ -134,7 +144,7 @@ def _hydrate_options(self, query_options: QnAMakerOptions) -> QnAMakerOptions:

async def _query_qna_service(
self, turn_context: TurnContext, options: QnAMakerOptions
) -> List[QueryResult]:
) -> QueryResults:
url = f"{ self._endpoint.host }/knowledgebases/{ self._endpoint.knowledge_base_id }/generateAnswer"

question = GenerateAnswerRequestBody(
Expand All @@ -152,7 +162,7 @@ async def _query_qna_service(
url, question, self._endpoint, options.timeout
)

result: List[QueryResult] = await self._format_qna_result(response, options)
result: QueryResults = await self._format_qna_result(response, options)

return result

Expand Down Expand Up @@ -182,7 +192,7 @@ async def _emit_trace_info(

async def _format_qna_result(
self, result, options: QnAMakerOptions
) -> List[QueryResult]:
) -> QueryResults:
json_res = result
if isinstance(result, ClientResponse):
json_res = await result.json()
Expand All @@ -200,4 +210,14 @@ async def _format_qna_result(
map(lambda answer: QueryResult(**answer), sorted_answers)
)

return answers_as_query_results
active_learning_enabled = (
json_res["activeLearningEnabled"]
if "activeLearningEnabled" in json_res
else True
)

query_answer_response = QueryResults(
answers_as_query_results, active_learning_enabled
)

return query_answer_response
8 changes: 7 additions & 1 deletion libraries/botbuilder-ai/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,13 @@
long_description=long_description,
long_description_content_type="text/x-rst",
license=package_info["__license__"],
packages=["botbuilder.ai", "botbuilder.ai.qna", "botbuilder.ai.luis"],
packages=[
"botbuilder.ai",
"botbuilder.ai.qna",
"botbuilder.ai.luis",
"botbuilder.ai.qna.models",
"botbuilder.ai.qna.utils",
],
install_requires=REQUIRES + TESTS_REQUIRES,
tests_require=TESTS_REQUIRES,
include_package_data=True,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"activeLearningEnabled": false,
"answers": [
{
"questions": [
Expand Down
36 changes: 36 additions & 0 deletions libraries/botbuilder-ai/tests/qna/test_qna.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,21 @@ async def test_returns_answer(self):
first_answer.answer,
)

async def test_active_learning_enabled_status(self):
# Arrange
question: str = "how do I clean the stove?"
response_path: str = "ReturnsAnswer.json"

# Act
result = await QnaApplicationTest._get_service_result_raw(
question, response_path
)

# Assert
self.assertIsNotNone(result)
self.assertEqual(1, len(result.answers))
self.assertFalse(result.active_learning_enabled)

async def test_returns_answer_using_options(self):
# Arrange
question: str = "up"
Expand Down Expand Up @@ -783,6 +798,27 @@ async def _get_service_result(

return result

@classmethod
async def _get_service_result_raw(
cls,
utterance: str,
response_file: str,
bot_adapter: BotAdapter = TestAdapter(),
options: QnAMakerOptions = None,
) -> [dict]:
response_json = QnaApplicationTest._get_json_for_file(response_file)

qna = QnAMaker(QnaApplicationTest.tests_endpoint)
context = QnaApplicationTest._get_context(utterance, bot_adapter)

with patch(
"aiohttp.ClientSession.post",
return_value=aiounittest.futurized(response_json),
):
result = await qna.get_answers_raw(context, options)

return result

@classmethod
def _get_json_for_file(cls, response_file: str) -> object:
curr_dir = path.dirname(path.abspath(__file__))
Expand Down