8000 [QnA Maker] Multi-turn support for python (#305) · gorpo/botbuilder-python@31dd7ac · GitHub
[go: up one dir, main page]

Skip to content

Commit 31dd7ac

Browse files
gurvsingaxelsrz
authored andcommitted
[QnA Maker] Multi-turn support for python (microsoft#305)
* [QnA Maker] Multi-turn support for python
1 parent 6340a92 commit 31dd7ac

14 files changed

+299
-25
lines changed

libraries/botbuilder-ai/botbuilder/ai/qna/models/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@
99
from .feedback_records import FeedbackRecords
1010
from .generate_answer_request_body import GenerateAnswerRequestBody
1111
from .metadata import Metadata
12+
from .prompt import Prompt
1213
from .qnamaker_trace_info import QnAMakerTraceInfo
14+
from .qna_request_context import QnARequestContext
15+
from .qna_response_context import QnAResponseContext
1316
from .query_result import QueryResult
1417
from .query_results import QueryResults
1518
from .train_request_body import TrainRequestBody
@@ -19,7 +22,10 @@
1922
"FeedbackRecords",
2023
"GenerateAnswerRequestBody",
2124
"Metadata",
25+
"Prompt",
2226
"QnAMakerTraceInfo",
27+
"QnARequestContext",
28+
"QnAResponseContext",
2329
"QueryResult",
2430
"QueryResults",
2531
"TrainRequestBody",

libraries/botbuilder-ai/botbuilder/ai/qna/models/generate_answer_request_body.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from msrest.serialization import Model
77

88
from .metadata import Metadata
9+
from .qna_request_context import QnARequestContext
910

1011

1112
class GenerateAnswerRequestBody(Model):
@@ -16,6 +17,7 @@ class GenerateAnswerRequestBody(Model):
1617
"top": {"key": "top", "type": "int"},
1718
"score_threshold": {"key": "scoreThreshold", "type": "float"},
1819
"strict_filters": {"key": "strictFilters", "type": "[Metadata]"},
20+
"context": {"key": "context", "type": "QnARequestContext"},
1921
}
2022

2123
def __init__(
@@ -24,6 +26,7 @@ def __init__(
2426
top: int,
2527
score_threshold: float,
2628
strict_filters: List[Metadata],
29+
context: QnARequestContext = None,
2730
**kwargs
2831
):
2932
"""
@@ -36,7 +39,10 @@ def __init__(
3639
3740
score_threshold: Threshold for answers returned based on score.
3841
39-
strict_filters: Find only answers that contain these metadata.
42+
strict_filters: Find answers that contains these metadata.
43+
44+
context: The context from which the QnA was extracted.
45+
4046
"""
4147

4248
super().__init__(**kwargs)
@@ -45,3 +51,4 @@ def __init__(
4551
self.top = top
4652
self.score_threshold = score_threshold
4753
self.strict_filters = strict_filters
54+
self.context = context
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from msrest.serialization import Model
5+
6+
7+
class Prompt(Model):
8+
""" Prompt Object. """
9+
10+
_attribute_map = {
11+
"display_order": {"key": "displayOrder", "type": "int"},
12+
"qna_id": {"key": "qnaId", "type": "int"},
13+
"qna": {"key": "qna", "type": "object"},
14+
"display_text": {"key": "displayText", "type": "str"},
15+
}
16+
17+
def __init__(
18+
self,
19+
*,
20+
display_order: int,
21+
qna_id: int,
22+
display_text: str,
23+
qna: object = None,
24+
**kwargs
25+
):
26+
"""
27+
Parameters:
28+
-----------
29+
30+
display_order: Index of the prompt - used in ordering of the prompts.
31+
32+
qna_id: QnA ID.
33+
34+
display_text: Text displayed to represent a follow up question prompt.
35+
36+
qna: The QnA object returned from the API (Optional).
37+
38+
"""
39+
40+
super(Prompt, self).__init__(**kwargs)
41+
42+
self.display_order = display_order
43+
self.qna_id = qna_id
44+
self.display_text = display_text
45+
self.qna = qna
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from msrest.serialization import Model
5+
6+
7+
class QnARequestContext(Model):
8+
"""
9+
The context associated with QnA.
10+
Used to mark if the current prompt is relevant with a previous question or not.
11+
"""
12+
13+
_attribute_map = {
14+
"previous_qna_id": {"key": "previousQnAId", "type": "int"},
15+
"prvious_user_query": {"key": "previousUserQuery", "type": "string"},
16+
}
17+
18+
def __init__(self, previous_qna_id: int, prvious_user_query: str, **kwargs):
19+
"""
20+
Parameters:
21+
-----------
22+
23+
previous_qna_id: The previous QnA Id that was returned.
24+
25+
prvious_user_query: The previous user query/question.
26+
"""
27+
28+
super().__init__(**kwargs)
29+
30+
self.previous_qna_id = previous_qna_id
31+
self.prvious_user_query = prvious_user_query
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import List
5+
from msrest.serialization import Model
6+
from .prompt import Prompt
7+
8+
9+
class QnAResponseContext(Model):
10+
"""
11+
The context associated with QnA.
12+
Used to mark if the qna response has related prompts.
13+
"""
14+
15+
_attribute_map = {
16+
"is_context_only": {"key": "isContextOnly", "type": "bool"},
17+
"prompts": {"key": "prompts", "type": "[Prompt]"},
18+
}
19+
20+
def __init__(
21+
self, *, is_context_only: bool = False, prompts: List[Prompt] = None, **kwargs
22+
):
23+
"""
24+
Parameters:
25+
-----------
26+
27+
is_context_only: Whether this prompt is context only.
28+
29+
prompts: The prompts collection of related prompts.
30+
31+
"""
32+
33+
super(QnAResponseContext, self).__init__(**kwargs)
34+
self.is_context_only = is_context_only
35+
self.prompts = prompts

libraries/botbuilder-ai/botbuilder/ai/qna/models/qnamaker_trace_info.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,11 @@
66
from botbuilder.schema import Activity
77
from .metadata import Metadata
88
from .query_result import QueryResult
9+
from .qna_request_context import QnARequestContext
910

1011

1112
class QnAMakerTraceInfo:
12-
""" Represents all the trice info that we collect from the QnAMaker Middleware. """
13+
""" Represents all the trace info that we collect from the QnAMaker Middleware. """
1314

1415
def __init__(
1516
self,
@@ -19,6 +20,7 @@ def __init__(
1920
score_threshold: float,
2021
top: int,
2122
strict_filters: List[Metadata],
23+
context: QnARequestContext = None,
2224
):
2325
"""
2426
Parameters:
@@ -35,10 +37,13 @@ def __init__(
3537
top: Number of ranked results that are asked to be returned.
3638
3739
strict_filters: Filters used on query.
40+
41+
context: (Optional) The context from which the QnA was extracted.
3842
"""
3943
self.message = message
4044
self.query_results = query_results
4145
self.knowledge_base_id = knowledge_base_id
4246
self.score_threshold = score_threshold
4347
self.top = top
4448
self.strict_filters = strict_filters
49+
self.context = context

libraries/botbuilder-ai/botbuilder/ai/qna/models/query_result.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,35 @@
22
# Licensed under the MIT License.
33

44
from typing import List
5-
5+
from msrest.serialization import Model
66
from .metadata import Metadata
7+
from .qna_response_context import QnAResponseContext
78

89

9-
class QueryResult:
10+
class QueryResult(Model):
1011
""" Represents an individual result from a knowledge base query. """
1112

13+
_attribute_map = {
14+
"questions": {"key": "questions", "type": "[str]"},
15+
"answer": {"key": "answer", "type": "str"},
16+
"score": {"key": "score", "type": "float"},
17+
"metadata": {"key": "metadata", "type": "object"},
18+
"source": {"key": "source", "type": "str"},
19+
"id": {"key": "id", "type": "int"},
20+
"context": {"key": "context", "type": "object"},
21+
}
22+
1223
def __init__(
1324
self,
25+
*,
1426
questions: List[str],
1527
answer: str,
1628
score: float,
1729
metadata: object = None,
1830
source: str = None,
1931
id: int = None, # pylint: disable=invalid-name
32+
context: QnAResponseContext = None,
33+
**kwargs
2034
):
2135
"""
2236
Parameters:
@@ -33,10 +47,14 @@ def __init__(
3347
source: The source from which the QnA was extracted (if any).
3448
3549
id: The index of the answer in the knowledge base. V3 uses 'qnaId', V4 uses 'id' (if any).
50+
51+
context: The context from which the QnA was extracted.
3652
"""
53+
super(QueryResult, self).__init__(**kwargs)
3754
self.questions = questions
3855
self.answer = answer
3956
self.score = score
4057
self.metadata = list(map(lambda meta: Metadata(**meta), metadata))
4158
self.source = source
59+
self.context = QnAResponseContext(**context) if context is not None else None
4260
self.id = id # pylint: disable=invalid-name

libraries/botbuilder-ai/botbuilder/ai/qna/qnamaker.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@ def __init__(
4747
"QnAMaker.__init__(): endpoint is not an instance of QnAMakerEndpoint"
4848
)
4949

50-
if endpoint.host.endswith("v2.0"):
51-
raise ValueError(
52-
"v2.0 of QnA Maker service is no longer supported in the Bot Framework. Please upgrade your QnA Maker"
53-
" service at www.qnamaker.ai."
54-
)
55-
5650
self._endpoint: str = endpoint
5751

5852
opt = options or QnAMakerOptions()
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
# Copyright (c) Microsoft Corporation. All rights reserved.
22
# Licensed under the MIT License.
33

4-
from .models import Metadata
5-
4+
from .models import Metadata, QnARequestContext
65

76
# figure out if 300 milliseconds is ok for python requests library...or 100000
87
class QnAMakerOptions:
@@ -12,8 +11,10 @@ def __init__(
1211
timeout: int = 0,
1312
top: int = 0,
1413
strict_filters: [Metadata] = None,
14+
context: [QnARequestContext] = None,
1515
):
1616
self.score_threshold = score_threshold
1717
self.timeout = timeout
1818
self.top = top
1919
self.strict_filters = strict_filters or []
20+
self.context = context

libraries/botbuilder-ai/botbuilder/ai/qna/utils/generate_answer_utils.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -127,18 +127,22 @@ def _hydrate_options(self, query_options: QnAMakerOptions) -> QnAMakerOptions:
127127
):
128128
hydrated_options.timeout = query_options.timeout
129129

130+
if query_options.context:
131+
hydrated_options.context = query_options.context
132+
130133
return hydrated_options
131134

132135
async def _query_qna_service(
133-
self, context: TurnContext, options: QnAMakerOptions
136+
self, turn_context: TurnContext, options: QnAMakerOptions
134137
) -> List[QueryResult]:
135138
url = f"{ self._endpoint.host }/knowledgebases/{ self._endpoint.knowledge_base_id }/generateAnswer"
136139

137140
question = GenerateAnswerRequestBody(
138-
question=context.activity.text,
141+
question=turn_context.activity.text,
139142
top=options.top,
140143
score_threshold=options.score_threshold,
141144
strict_filters=options.strict_filters,
145+
context=options.context,
142146
)
143147

144148
http_request_helper = HttpRequestUtils(self._http_client)
@@ -161,6 +165,7 @@ async def _emit_trace_info(
161165
score_threshold=options.score_threshold,
162166
top=options.top,
163167
strict_filters=options.strict_filters,
168+
context=options.context,
164169
)
165170

166171
trace_activity = Activity(
@@ -189,15 +194,6 @@ async def _format_qna_result(
189194
answers_within_threshold, key=lambda ans: ans["score"], reverse=True
190195
)
191196

192-
# The old version of the protocol returns the id in a field called qnaId
193-
# The following translates this old structure to the new
194-
is_legacy_protocol: bool = self._endpoint.host.endswith(
195-
"v2.0"
196-
) or self._endpoint.host.endswith("v3.0")
197-
if is_legacy_protocol:
198-
for answer in answers_within_threshold:
199-
answer["id"] = answer.pop("qnaId", None)
200-
201197
answers_as_query_results = list(
202198
map(lambda answer: QueryResult(**answer), sorted_answers)
203199
)

0 commit comments

Comments
 (0)
0