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
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from .metadata import Metadata
from .qna_request_context import QnARequestContext
from .ranker_types import RankerTypes


class GenerateAnswerRequestBody(Model):
Expand All @@ -19,6 +20,8 @@ class GenerateAnswerRequestBody(Model):
"strict_filters": {"key": "strictFilters", "type": "[Metadata]"},
"context": {"key": "context", "type": "QnARequestContext"},
"qna_id": {"key": "qnaId", "type": "int"},
"is_test": {"key": "isTest", "type": "bool"},
"ranker_type": {"key": "rankerType", "type": "RankerTypes"},
}

def __init__(
Expand All @@ -29,6 +32,8 @@ def __init__(
strict_filters: List[Metadata],
context: QnARequestContext = None,
qna_id: int = None,
is_test: bool = False,
ranker_type: str = RankerTypes.DEFAULT,
**kwargs
):
"""
Expand All @@ -47,6 +52,10 @@ def __init__(

qna_id: Id of the current question asked.

is_test: (Optional) A value indicating whether to call test or prod environment of knowledgebase.

ranker_types: (Optional) Ranker types.

"""

super().__init__(**kwargs)
Expand All @@ -57,3 +66,5 @@ def __init__(
self.strict_filters = strict_filters
self.context = context
self.qna_id = qna_id
self.is_test = is_test
self.ranker_type = ranker_type
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from .metadata import Metadata
from .query_result import QueryResult
from .qna_request_context import QnARequestContext
from .ranker_types import RankerTypes


class QnAMakerTraceInfo:
Expand All @@ -22,6 +23,8 @@ def __init__(
strict_filters: List[Metadata],
context: QnARequestContext = None,
qna_id: int = None,
is_test: bool = False,
ranker_type: str = RankerTypes.DEFAULT,
):
"""
Parameters:
Expand All @@ -42,6 +45,10 @@ def __init__(
context: (Optional) The context from which the QnA was extracted.

qna_id: (Optional) Id of the current question asked.

is_test: (Optional) A value indicating whether to call test or prod environment of knowledgebase.

ranker_types: (Optional) Ranker types.
"""
self.message = message
self.query_results = query_results
Expand All @@ -51,3 +58,5 @@ def __init__(
self.strict_filters = strict_filters
self.context = context
self.qna_id = qna_id
self.is_test = is_test
self.ranker_type = ranker_type
15 changes: 15 additions & 0 deletions libraries/botbuilder-ai/botbuilder/ai/qna/models/ranker_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.


class RankerTypes:

""" Default Ranker Behaviour. i.e. Ranking based on Questions and Answer. """

DEFAULT = "Default"

""" Ranker based on question Only. """
QUESTION_ONLY = "QuestionOnly"

""" Ranker based on Autosuggest for question field only. """
AUTO_SUGGEST_QUESTION = "AutoSuggestQuestion"
5 changes: 5 additions & 0 deletions libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Licensed under the MIT License.

from .models import Metadata, QnARequestContext
from .models.ranker_types import RankerTypes

# figure out if 300 milliseconds is ok for python requests library...or 100000
class QnAMakerOptions:
Expand All @@ -13,10 +14,14 @@ def __init__(
strict_filters: [Metadata] = None,
context: [QnARequestContext] = None,
qna_id: int = None,
is_test: bool = False,
ranker_type: bool = RankerTypes.DEFAULT,
):
self.score_threshold = score_threshold
self.timeout = timeout
self.top = top
self.strict_filters = strict_filters or []
self.context = context
self.qna_id = qna_id
self.is_test = is_test
self.ranker_type = ranker_type
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ def _hydrate_options(self, query_options: QnAMakerOptions) -> QnAMakerOptions:

hydrated_options.context = query_options.context
hydrated_options.qna_id = query_options.qna_id
hydrated_options.is_test = query_options.is_test
hydrated_options.ranker_type = query_options.ranker_type

return hydrated_options

Expand All @@ -154,6 +156,8 @@ async def _query_qna_service(
strict_filters=options.strict_filters,
context=options.context,
qna_id=options.qna_id,
is_test=options.is_test,
ranker_type=options.ranker_type,
)

http_request_helper = HttpRequestUtils(self._http_client)
Expand All @@ -178,6 +182,8 @@ async def _emit_trace_info(
strict_filters=options.strict_filters,
context=options.context,
qna_id=options.qna_id,
is_test=options.is_test,
ranker_type=options.ranker_type,
)

trace_activity = Activity(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"activeLearningEnabled": true,
"answers": [
{
"questions": [],
"answer": "No good match found in KB.",
"score": 0,
"id": -1,
"source": null,
"metadata": []
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"activeLearningEnabled": false,
"answers": [
{
"questions": [
"Q1"
],
"answer": "A1",
"score": 80,
"id": 15,
"source": "Editorial",
"metadata": [
{
"name": "topic",
"value": "value"
}
]
},
{
"questions": [
"Q2"
],
"answer": "A2",
"score": 78,
"id": 16,
"source": "Editorial",
"metadata": [
{
"name": "topic",
"value": "value"
}
]
}
]
}
32 changes: 32 additions & 0 deletions libraries/botbuilder-ai/tests/qna/test_qna.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,38 @@ async def test_should_filter_low_score_variation(self):
"Should have 3 filtered answers after low score variation.",
)

async def test_should_answer_with_is_test_true(self):
options = QnAMakerOptions(top=1, is_test=True)
qna = QnAMaker(QnaApplicationTest.tests_endpoint)
question: str = "Q11"
context = QnaApplicationTest._get_context(question, TestAdapter())
response_json = QnaApplicationTest._get_json_for_file(
"QnaMaker_IsTest_true.json"
)

with patch(
"aiohttp.ClientSession.post",
return_value=aiounittest.futurized(response_json),
):
results = await qna.get_answers(context, options=options)
self.assertEqual(0, len(results), "Should have received zero answer.")

async def test_should_answer_with_ranker_type_question_only(self):
options = QnAMakerOptions(top=1, ranker_type="QuestionOnly")
qna = QnAMaker(QnaApplicationTest.tests_endpoint)
question: str = "Q11"
context = QnaApplicationTest._get_context(question, TestAdapter())
response_json = QnaApplicationTest._get_json_for_file(
"QnaMaker_RankerType_QuestionOnly.json"
)

with patch(
"aiohttp.ClientSession.post",
return_value=aiounittest.futurized(response_json),
):
results = await qna.get_answers(context, options=options)
self.assertEqual(2, len(results), "Should have received two answers.")

async def test_should_answer_with_prompts(self):
options = QnAMakerOptions(top=2)
qna = QnAMaker(QnaApplicationTest.tests_endpoint, options)
Expand Down