diff --git a/google/cloud/dialogflow_v2beta1/__init__.py b/google/cloud/dialogflow_v2beta1/__init__.py index e67e8454..1adabe7b 100644 --- a/google/cloud/dialogflow_v2beta1/__init__.py +++ b/google/cloud/dialogflow_v2beta1/__init__.py @@ -105,6 +105,9 @@ ListConversationsResponse, ListMessagesRequest, ListMessagesResponse, + SearchKnowledgeAnswer, + SearchKnowledgeRequest, + SearchKnowledgeResponse, SuggestConversationSummaryRequest, SuggestConversationSummaryResponse, ) @@ -467,6 +470,9 @@ "RestoreAgentRequest", "SearchAgentsRequest", "SearchAgentsResponse", + "SearchKnowledgeAnswer", + "SearchKnowledgeRequest", + "SearchKnowledgeResponse", "Sentiment", "SentimentAnalysisRequestConfig", "SentimentAnalysisResult", diff --git a/google/cloud/dialogflow_v2beta1/gapic_metadata.json b/google/cloud/dialogflow_v2beta1/gapic_metadata.json index 43a0d4c9..3cd78643 100644 --- a/google/cloud/dialogflow_v2beta1/gapic_metadata.json +++ b/google/cloud/dialogflow_v2beta1/gapic_metadata.json @@ -496,6 +496,11 @@ "list_messages" ] }, + "SearchKnowledge": { + "methods": [ + "search_knowledge" + ] + }, "SuggestConversationSummary": { "methods": [ "suggest_conversation_summary" @@ -541,6 +546,11 @@ "list_messages" ] }, + "SearchKnowledge": { + "methods": [ + "search_knowledge" + ] + }, "SuggestConversationSummary": { "methods": [ "suggest_conversation_summary" @@ -586,6 +596,11 @@ "list_messages" ] }, + "SearchKnowledge": { + "methods": [ + "search_knowledge" + ] + }, "SuggestConversationSummary": { "methods": [ "suggest_conversation_summary" diff --git a/google/cloud/dialogflow_v2beta1/services/conversation_profiles/async_client.py b/google/cloud/dialogflow_v2beta1/services/conversation_profiles/async_client.py index ca11dd6f..ff523510 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversation_profiles/async_client.py +++ b/google/cloud/dialogflow_v2beta1/services/conversation_profiles/async_client.py @@ -1040,7 +1040,7 @@ async def sample_clear_suggestion_feature_config(): request = dialogflow_v2beta1.ClearSuggestionFeatureConfigRequest( conversation_profile="conversation_profile_value", participant_role="END_USER", - suggestion_feature_type="CONVERSATION_SUMMARIZATION", + suggestion_feature_type="KNOWLEDGE_SEARCH", ) # Make the request diff --git a/google/cloud/dialogflow_v2beta1/services/conversation_profiles/client.py b/google/cloud/dialogflow_v2beta1/services/conversation_profiles/client.py index 958915fb..9fe02ac5 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversation_profiles/client.py +++ b/google/cloud/dialogflow_v2beta1/services/conversation_profiles/client.py @@ -1364,7 +1364,7 @@ def sample_clear_suggestion_feature_config(): request = dialogflow_v2beta1.ClearSuggestionFeatureConfigRequest( conversation_profile="conversation_profile_value", participant_role="END_USER", - suggestion_feature_type="CONVERSATION_SUMMARIZATION", + suggestion_feature_type="KNOWLEDGE_SEARCH", ) # Make the request diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/async_client.py b/google/cloud/dialogflow_v2beta1/services/conversations/async_client.py index fb293994..17f2a2f7 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/async_client.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/async_client.py @@ -1165,6 +1165,87 @@ async def sample_generate_stateless_summary(): # Done; return the response. return response + async def search_knowledge( + self, + request: Optional[Union[conversation.SearchKnowledgeRequest, dict]] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, str]] = (), + ) -> conversation.SearchKnowledgeResponse: + r"""Get answers for the given query based on knowledge + documents. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import dialogflow_v2beta1 + + async def sample_search_knowledge(): + # Create a client + client = dialogflow_v2beta1.ConversationsAsyncClient() + + # Initialize request argument(s) + request = dialogflow_v2beta1.SearchKnowledgeRequest( + conversation_profile="conversation_profile_value", + ) + + # Make the request + response = await client.search_knowledge(request=request) + + # Handle the response + print(response) + + Args: + request (Optional[Union[google.cloud.dialogflow_v2beta1.types.SearchKnowledgeRequest, dict]]): + The request object. The request message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + google.cloud.dialogflow_v2beta1.types.SearchKnowledgeResponse: + The response message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + + """ + # Create or coerce a protobuf request object. + request = conversation.SearchKnowledgeRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = gapic_v1.method_async.wrap_method( + self._client._transport.search_knowledge, + default_timeout=None, + client_info=DEFAULT_CLIENT_INFO, + ) + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = await rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + async def list_operations( self, request: Optional[operations_pb2.ListOperationsRequest] = None, diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/client.py b/google/cloud/dialogflow_v2beta1/services/conversations/client.py index 6b1e20fd..d46f0d4a 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/client.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/client.py @@ -1527,6 +1527,88 @@ def sample_generate_stateless_summary(): # Done; return the response. return response + def search_knowledge( + self, + request: Optional[Union[conversation.SearchKnowledgeRequest, dict]] = None, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Union[float, object] = gapic_v1.method.DEFAULT, + metadata: Sequence[Tuple[str, str]] = (), + ) -> conversation.SearchKnowledgeResponse: + r"""Get answers for the given query based on knowledge + documents. + + .. code-block:: python + + # This snippet has been automatically generated and should be regarded as a + # code template only. + # It will require modifications to work: + # - It may require correct/in-range values for request initialization. + # - It may require specifying regional endpoints when creating the service + # client as shown in: + # https://googleapis.dev/python/google-api-core/latest/client_options.html + from google.cloud import dialogflow_v2beta1 + + def sample_search_knowledge(): + # Create a client + client = dialogflow_v2beta1.ConversationsClient() + + # Initialize request argument(s) + request = dialogflow_v2beta1.SearchKnowledgeRequest( + conversation_profile="conversation_profile_value", + ) + + # Make the request + response = client.search_knowledge(request=request) + + # Handle the response + print(response) + + Args: + request (Union[google.cloud.dialogflow_v2beta1.types.SearchKnowledgeRequest, dict]): + The request object. The request message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + google.cloud.dialogflow_v2beta1.types.SearchKnowledgeResponse: + The response message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + + """ + # Create or coerce a protobuf request object. + # Minor optimization to avoid making a copy if the user passes + # in a conversation.SearchKnowledgeRequest. + # There's no risk of modifying the input as we've already verified + # there are no flattened fields. + if not isinstance(request, conversation.SearchKnowledgeRequest): + request = conversation.SearchKnowledgeRequest(request) + + # Wrap the RPC method; this adds retry and timeout information, + # and friendly error handling. + rpc = self._transport._wrapped_methods[self._transport.search_knowledge] + + # Certain fields should be provided within the metadata header; + # add these here. + metadata = tuple(metadata) + ( + gapic_v1.routing_header.to_grpc_metadata((("parent", request.parent),)), + ) + + # Send the request. + response = rpc( + request, + retry=retry, + timeout=timeout, + metadata=metadata, + ) + + # Done; return the response. + return response + def __enter__(self) -> "ConversationsClient": return self diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/transports/base.py b/google/cloud/dialogflow_v2beta1/services/conversations/transports/base.py index e5857c93..47323ca2 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/transports/base.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/transports/base.py @@ -168,6 +168,11 @@ def _prep_wrapped_messages(self, client_info): default_timeout=None, client_info=client_info, ), + self.search_knowledge: gapic_v1.method.wrap_method( + self.search_knowledge, + default_timeout=None, + client_info=client_info, + ), } def close(self): @@ -266,6 +271,18 @@ def generate_stateless_summary( ]: raise NotImplementedError() + @property + def search_knowledge( + self, + ) -> Callable[ + [conversation.SearchKnowledgeRequest], + Union[ + conversation.SearchKnowledgeResponse, + Awaitable[conversation.SearchKnowledgeResponse], + ], + ]: + raise NotImplementedError() + @property def list_operations( self, diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc.py b/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc.py index 4016b7df..e2229a67 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc.py @@ -489,6 +489,35 @@ def generate_stateless_summary( ) return self._stubs["generate_stateless_summary"] + @property + def search_knowledge( + self, + ) -> Callable[ + [conversation.SearchKnowledgeRequest], conversation.SearchKnowledgeResponse + ]: + r"""Return a callable for the search knowledge method over gRPC. + + Get answers for the given query based on knowledge + documents. + + Returns: + Callable[[~.SearchKnowledgeRequest], + ~.SearchKnowledgeResponse]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_knowledge" not in self._stubs: + self._stubs["search_knowledge"] = self.grpc_channel.unary_unary( + "/google.cloud.dialogflow.v2beta1.Conversations/SearchKnowledge", + request_serializer=conversation.SearchKnowledgeRequest.serialize, + response_deserializer=conversation.SearchKnowledgeResponse.deserialize, + ) + return self._stubs["search_knowledge"] + def close(self): self.grpc_channel.close() diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc_asyncio.py b/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc_asyncio.py index ca23a671..dd8fd60b 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc_asyncio.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/transports/grpc_asyncio.py @@ -496,6 +496,36 @@ def generate_stateless_summary( ) return self._stubs["generate_stateless_summary"] + @property + def search_knowledge( + self, + ) -> Callable[ + [conversation.SearchKnowledgeRequest], + Awaitable[conversation.SearchKnowledgeResponse], + ]: + r"""Return a callable for the search knowledge method over gRPC. + + Get answers for the given query based on knowledge + documents. + + Returns: + Callable[[~.SearchKnowledgeRequest], + Awaitable[~.SearchKnowledgeResponse]]: + A function that, when called, will call the underlying RPC + on the server. + """ + # Generate a "stub function" on-the-fly which will actually make + # the request. + # gRPC handles serialization and deserialization, so we just need + # to pass in the functions for each. + if "search_knowledge" not in self._stubs: + self._stubs["search_knowledge"] = self.grpc_channel.unary_unary( + "/google.cloud.dialogflow.v2beta1.Conversations/SearchKnowledge", + request_serializer=conversation.SearchKnowledgeRequest.serialize, + response_deserializer=conversation.SearchKnowledgeResponse.deserialize, + ) + return self._stubs["search_knowledge"] + def close(self): return self.grpc_channel.close() diff --git a/google/cloud/dialogflow_v2beta1/services/conversations/transports/rest.py b/google/cloud/dialogflow_v2beta1/services/conversations/transports/rest.py index e5691a5f..972bd780 100644 --- a/google/cloud/dialogflow_v2beta1/services/conversations/transports/rest.py +++ b/google/cloud/dialogflow_v2beta1/services/conversations/transports/rest.py @@ -122,6 +122,14 @@ def post_list_messages(self, response): logging.log(f"Received response: {response}") return response + def pre_search_knowledge(self, request, metadata): + logging.log(f"Received request: {request}") + return request, metadata + + def post_search_knowledge(self, response): + logging.log(f"Received response: {response}") + return response + def pre_suggest_conversation_summary(self, request, metadata): logging.log(f"Received request: {request}") return request, metadata @@ -297,6 +305,29 @@ def post_list_messages( """ return response + def pre_search_knowledge( + self, + request: conversation.SearchKnowledgeRequest, + metadata: Sequence[Tuple[str, str]], + ) -> Tuple[conversation.SearchKnowledgeRequest, Sequence[Tuple[str, str]]]: + """Pre-rpc interceptor for search_knowledge + + Override in a subclass to manipulate the request or metadata + before they are sent to the Conversations server. + """ + return request, metadata + + def post_search_knowledge( + self, response: conversation.SearchKnowledgeResponse + ) -> conversation.SearchKnowledgeResponse: + """Post-rpc interceptor for search_knowledge + + Override in a subclass to manipulate the response + after it is returned by the Conversations server but before + it is returned to user code. + """ + return response + def pre_suggest_conversation_summary( self, request: gcd_conversation.SuggestConversationSummaryRequest, @@ -1252,6 +1283,121 @@ def __call__( resp = self._interceptor.post_list_messages(resp) return resp + class _SearchKnowledge(ConversationsRestStub): + def __hash__(self): + return hash("SearchKnowledge") + + __REQUIRED_FIELDS_DEFAULT_VALUES: Dict[str, Any] = {} + + @classmethod + def _get_unset_required_fields(cls, message_dict): + return { + k: v + for k, v in cls.__REQUIRED_FIELDS_DEFAULT_VALUES.items() + if k not in message_dict + } + + def __call__( + self, + request: conversation.SearchKnowledgeRequest, + *, + retry: OptionalRetry = gapic_v1.method.DEFAULT, + timeout: Optional[float] = None, + metadata: Sequence[Tuple[str, str]] = (), + ) -> conversation.SearchKnowledgeResponse: + r"""Call the search knowledge method over HTTP. + + Args: + request (~.conversation.SearchKnowledgeRequest): + The request object. The request message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + retry (google.api_core.retry.Retry): Designation of what errors, if any, + should be retried. + timeout (float): The timeout for this request. + metadata (Sequence[Tuple[str, str]]): Strings which should be + sent along with the request as metadata. + + Returns: + ~.conversation.SearchKnowledgeResponse: + The response message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + + """ + + http_options: List[Dict[str, str]] = [ + { + "method": "post", + "uri": "/v2beta1/{parent=projects/*}/suggestions:searchKnowledge", + "body": "*", + }, + { + "method": "post", + "uri": "/v2beta1/{parent=projects/*/locations/*}/suggestions:searchKnowledge", + "body": "*", + }, + { + "method": "post", + "uri": "/v2beta1/{conversation=projects/*/conversations/*}/suggestions:searchKnowledge", + "body": "*", + }, + { + "method": "post", + "uri": "/v2beta1/{conversation=projects/*/locations/*/conversations/*}/suggestions:searchKnowledge", + "body": "*", + }, + ] + request, metadata = self._interceptor.pre_search_knowledge( + request, metadata + ) + pb_request = conversation.SearchKnowledgeRequest.pb(request) + transcoded_request = path_template.transcode(http_options, pb_request) + + # Jsonify the request body + + body = json_format.MessageToJson( + transcoded_request["body"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + uri = transcoded_request["uri"] + method = transcoded_request["method"] + + # Jsonify the query params + query_params = json.loads( + json_format.MessageToJson( + transcoded_request["query_params"], + including_default_value_fields=False, + use_integers_for_enums=True, + ) + ) + query_params.update(self._get_unset_required_fields(query_params)) + + query_params["$alt"] = "json;enum-encoding=int" + + # Send the request + headers = dict(metadata) + headers["Content-Type"] = "application/json" + response = getattr(self._session, method)( + "{host}{uri}".format(host=self._host, uri=uri), + timeout=timeout, + headers=headers, + params=rest_helpers.flatten_query_params(query_params, strict=True), + data=body, + ) + + # In case of error, raise the appropriate core_exceptions.GoogleAPICallError exception + # subclass. + if response.status_code >= 400: + raise core_exceptions.from_http_response(response) + + # Return the response + resp = conversation.SearchKnowledgeResponse() + pb_resp = conversation.SearchKnowledgeResponse.pb(resp) + + json_format.Parse(response.content, pb_resp, ignore_unknown_fields=True) + resp = self._interceptor.post_search_knowledge(resp) + return resp + class _SuggestConversationSummary(ConversationsRestStub): def __hash__(self): return hash("SuggestConversationSummary") @@ -1428,6 +1574,16 @@ def list_messages( # In C++ this would require a dynamic_cast return self._ListMessages(self._session, self._host, self._interceptor) # type: ignore + @property + def search_knowledge( + self, + ) -> Callable[ + [conversation.SearchKnowledgeRequest], conversation.SearchKnowledgeResponse + ]: + # The return type is fine, but mypy isn't sophisticated enough to determine what's going on here. + # In C++ this would require a dynamic_cast + return self._SearchKnowledge(self._session, self._host, self._interceptor) # type: ignore + @property def suggest_conversation_summary( self, diff --git a/google/cloud/dialogflow_v2beta1/types/__init__.py b/google/cloud/dialogflow_v2beta1/types/__init__.py index b1ff178b..6e842d15 100644 --- a/google/cloud/dialogflow_v2beta1/types/__init__.py +++ b/google/cloud/dialogflow_v2beta1/types/__init__.py @@ -79,6 +79,9 @@ ListConversationsResponse, ListMessagesRequest, ListMessagesResponse, + SearchKnowledgeAnswer, + SearchKnowledgeRequest, + SearchKnowledgeResponse, SuggestConversationSummaryRequest, SuggestConversationSummaryResponse, ) @@ -309,6 +312,9 @@ "ListConversationsResponse", "ListMessagesRequest", "ListMessagesResponse", + "SearchKnowledgeAnswer", + "SearchKnowledgeRequest", + "SearchKnowledgeResponse", "SuggestConversationSummaryRequest", "SuggestConversationSummaryResponse", "ConversationEvent", diff --git a/google/cloud/dialogflow_v2beta1/types/answer_record.py b/google/cloud/dialogflow_v2beta1/types/answer_record.py index d8569060..8c1a0ba2 100644 --- a/google/cloud/dialogflow_v2beta1/types/answer_record.py +++ b/google/cloud/dialogflow_v2beta1/types/answer_record.py @@ -267,6 +267,8 @@ class AgentAssistantFeedback(proto.Message): [DocumentEfficiency.INEFFICIENT][google.cloud.dialogflow.v2beta1.AgentAssistantFeedback.DocumentEfficiency.INEFFICIENT]. summarization_feedback (google.cloud.dialogflow_v2beta1.types.AgentAssistantFeedback.SummarizationFeedback): Feedback for conversation summarization. + knowledge_search_feedback (google.cloud.dialogflow_v2beta1.types.AgentAssistantFeedback.KnowledgeSearchFeedback): + Optional. Feedback for knowledge search. """ class AnswerRelevance(proto.Enum): @@ -342,6 +344,32 @@ class SummarizationFeedback(proto.Message): number=3, ) + class KnowledgeSearchFeedback(proto.Message): + r"""Feedback for knowledge search. + + Attributes: + answer_copied (bool): + Whether the answer was copied by the human agent or not. If + the value is set to be true, + [AnswerFeedback.clicked][google.cloud.dialogflow.v2beta1.AnswerFeedback.clicked] + will be updated to be true. + clicked_uris (MutableSequence[str]): + The URIs clicked by the human agent. The value is appended + for each UpdateAnswerRecordRequest. If the value is not + empty, + [AnswerFeedback.clicked][google.cloud.dialogflow.v2beta1.AnswerFeedback.clicked] + will be updated to be true. + """ + + answer_copied: bool = proto.Field( + proto.BOOL, + number=1, + ) + clicked_uris: MutableSequence[str] = proto.RepeatedField( + proto.STRING, + number=2, + ) + answer_relevance: AnswerRelevance = proto.Field( proto.ENUM, number=1, @@ -362,6 +390,11 @@ class SummarizationFeedback(proto.Message): number=4, message=SummarizationFeedback, ) + knowledge_search_feedback: KnowledgeSearchFeedback = proto.Field( + proto.MESSAGE, + number=5, + message=KnowledgeSearchFeedback, + ) class GetAnswerRecordRequest(proto.Message): diff --git a/google/cloud/dialogflow_v2beta1/types/audio_config.py b/google/cloud/dialogflow_v2beta1/types/audio_config.py index 400b411f..c614d994 100644 --- a/google/cloud/dialogflow_v2beta1/types/audio_config.py +++ b/google/cloud/dialogflow_v2beta1/types/audio_config.py @@ -743,6 +743,10 @@ class SpeechToTextConfig(proto.Message): `Cloud Speech API documentation `__ for more details. + use_timeout_based_endpointing (bool): + Use timeout based endpointing, interpreting + endpointer sensitivy as seconds of timeout + value. """ speech_model_variant: "SpeechModelVariant" = proto.Field( @@ -754,6 +758,10 @@ class SpeechToTextConfig(proto.Message): proto.STRING, number=2, ) + use_timeout_based_endpointing: bool = proto.Field( + proto.BOOL, + number=11, + ) __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflow_v2beta1/types/conversation.py b/google/cloud/dialogflow_v2beta1/types/conversation.py index 7ee33ee9..5981f4aa 100644 --- a/google/cloud/dialogflow_v2beta1/types/conversation.py +++ b/google/cloud/dialogflow_v2beta1/types/conversation.py @@ -23,7 +23,7 @@ from google.cloud.dialogflow_v2beta1.types import ( conversation_profile as gcd_conversation_profile, ) -from google.cloud.dialogflow_v2beta1.types import participant +from google.cloud.dialogflow_v2beta1.types import participant, session __protobuf__ = proto.module( package="google.cloud.dialogflow.v2beta1", @@ -44,6 +44,9 @@ "SuggestConversationSummaryResponse", "GenerateStatelessSummaryRequest", "GenerateStatelessSummaryResponse", + "SearchKnowledgeRequest", + "SearchKnowledgeResponse", + "SearchKnowledgeAnswer", }, ) @@ -752,4 +755,160 @@ class Summary(proto.Message): ) +class SearchKnowledgeRequest(proto.Message): + r"""The request message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + + Attributes: + parent (str): + The parent resource contains the conversation profile + Format: 'projects/' or + ``projects//locations/``. + query (google.cloud.dialogflow_v2beta1.types.TextInput): + Required. The natural language text query for + knowledge search. + conversation_profile (str): + Required. The conversation profile used to configure the + search. Format: + ``projects//locations//conversationProfiles/``. + session_id (str): + The ID of the search session. The session_id can be combined + with Dialogflow V3 Agent ID retrieved from conversation + profile or on its own to identify a search session. The + search history of the same session will impact the search + result. It's up to the API caller to choose an appropriate + ``Session ID``. It can be a random number or some type of + session identifiers (preferably hashed). The length must not + exceed 36 characters. + conversation (str): + The conversation (between human agent and end user) where + the search request is triggered. Format: + ``projects//locations//conversations/``. + latest_message (str): + The name of the latest conversation message when the request + is triggered. Format: + ``projects//locations//conversations//messages/``. + """ + + parent: str = proto.Field( + proto.STRING, + number=6, + ) + query: session.TextInput = proto.Field( + proto.MESSAGE, + number=1, + message=session.TextInput, + ) + conversation_profile: str = proto.Field( + proto.STRING, + number=2, + ) + session_id: str = proto.Field( + proto.STRING, + number=3, + ) + conversation: str = proto.Field( + proto.STRING, + number=4, + ) + latest_message: str = proto.Field( + proto.STRING, + number=5, + ) + + +class SearchKnowledgeResponse(proto.Message): + r"""The response message for + [Conversations.SearchKnowledge][google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge]. + + Attributes: + answers (MutableSequence[google.cloud.dialogflow_v2beta1.types.SearchKnowledgeAnswer]): + Most relevant snippets extracted from + articles in the given knowledge base, ordered by + confidence. + """ + + answers: MutableSequence["SearchKnowledgeAnswer"] = proto.RepeatedField( + proto.MESSAGE, + number=2, + message="SearchKnowledgeAnswer", + ) + + +class SearchKnowledgeAnswer(proto.Message): + r"""Represents a SearchKnowledge answer. + + Attributes: + answer (str): + The piece of text from the knowledge base + documents that answers the search query + answer_type (google.cloud.dialogflow_v2beta1.types.SearchKnowledgeAnswer.AnswerType): + The type of the answer. + answer_sources (MutableSequence[google.cloud.dialogflow_v2beta1.types.SearchKnowledgeAnswer.AnswerSource]): + All sources used to generate the answer. + answer_record (str): + The name of the answer record. Format: + ``projects//locations//answer Records/`` + """ + + class AnswerType(proto.Enum): + r"""The type of the answer. + + Values: + ANSWER_TYPE_UNSPECIFIED (0): + The answer has a unspecified type. + FAQ (1): + The answer is from FAQ doucments. + GENERATIVE (2): + The answer is from generative model. + """ + ANSWER_TYPE_UNSPECIFIED = 0 + FAQ = 1 + GENERATIVE = 2 + + class AnswerSource(proto.Message): + r"""The sources of the answers. + + Attributes: + title (str): + The title of the article. + uri (str): + The URI of the article. + snippet (str): + The relevant snippet of the article. + """ + + title: str = proto.Field( + proto.STRING, + number=1, + ) + uri: str = proto.Field( + proto.STRING, + number=2, + ) + snippet: str = proto.Field( + proto.STRING, + number=3, + ) + + answer: str = proto.Field( + proto.STRING, + number=1, + ) + answer_type: AnswerType = proto.Field( + proto.ENUM, + number=2, + enum=AnswerType, + ) + answer_sources: MutableSequence[AnswerSource] = proto.RepeatedField( + proto.MESSAGE, + number=3, + message=AnswerSource, + ) + answer_record: str = proto.Field( + proto.STRING, + number=5, + ) + + __all__ = tuple(sorted(__protobuf__.manifest)) diff --git a/google/cloud/dialogflow_v2beta1/types/conversation_profile.py b/google/cloud/dialogflow_v2beta1/types/conversation_profile.py index ce596ba0..73f400bc 100644 --- a/google/cloud/dialogflow_v2beta1/types/conversation_profile.py +++ b/google/cloud/dialogflow_v2beta1/types/conversation_profile.py @@ -278,7 +278,13 @@ class SuggestionFeatureConfig(proto.Message): suggestions. Supported features: ARTICLE_SUGGESTION, FAQ, - DIALOGFLOW_ASSIST, ENTITY_EXTRACTION. + DIALOGFLOW_ASSIST, ENTITY_EXTRACTION, KNOWLEDGE_ASSIST. + disable_agent_query_logging (bool): + Optional. Disable the logging of search queries sent by + human agents. It can prevent those queries from being stored + at answer records. + + Supported features: KNOWLEDGE_SEARCH. suggestion_trigger_settings (google.cloud.dialogflow_v2beta1.types.HumanAgentAssistantConfig.SuggestionTriggerSettings): Settings of suggestion trigger. @@ -301,6 +307,10 @@ class SuggestionFeatureConfig(proto.Message): proto.BOOL, number=3, ) + disable_agent_query_logging: bool = proto.Field( + proto.BOOL, + number=14, + ) suggestion_trigger_settings: "HumanAgentAssistantConfig.SuggestionTriggerSettings" = proto.Field( proto.MESSAGE, number=10, diff --git a/google/cloud/dialogflow_v2beta1/types/participant.py b/google/cloud/dialogflow_v2beta1/types/participant.py index 80487163..7a5ad173 100644 --- a/google/cloud/dialogflow_v2beta1/types/participant.py +++ b/google/cloud/dialogflow_v2beta1/types/participant.py @@ -688,6 +688,9 @@ class Type(proto.Enum): CONVERSATION_SUMMARIZATION (8): Run conversation summarization model for chat. + KNOWLEDGE_SEARCH (14): + Run knowledge search with text input from + agent or text generated query. """ TYPE_UNSPECIFIED = 0 ARTICLE_SUGGESTION = 1 @@ -695,6 +698,7 @@ class Type(proto.Enum): SMART_REPLY = 3 DIALOGFLOW_ASSIST = 4 CONVERSATION_SUMMARIZATION = 8 + KNOWLEDGE_SEARCH = 14 type_: Type = proto.Field( proto.ENUM, @@ -1106,7 +1110,8 @@ class StreamingAnalyzeContentRequest(proto.Message): Must be sent if ``text_config`` is set in the first message. Text length must not exceed 256 bytes for virtual agent interactions. The ``input_text`` field can be only sent - once. + once, and would cancel the speech recognition if any + ongoing. This field is a member of `oneof`_ ``input``. input_dtmf (google.cloud.dialogflow_v2beta1.types.TelephonyDtmfEvents): diff --git a/google/cloud/dialogflow_v2beta1/types/session.py b/google/cloud/dialogflow_v2beta1/types/session.py index 6b60146c..8b0dda9a 100644 --- a/google/cloud/dialogflow_v2beta1/types/session.py +++ b/google/cloud/dialogflow_v2beta1/types/session.py @@ -897,8 +897,10 @@ class CloudConversationDebuggingInfo(proto.Message): Time offset of the end-of-single-utterance signal relative to the beginning of the stream. no_speech_timeout (google.protobuf.duration_pb2.Duration): - No speech timeout settings observed at - runtime. + No speech timeout settings for the stream. + endpointing_timeout (google.protobuf.duration_pb2.Duration): + Speech endpointing timeout settings for the + stream. is_input_text (bool): Whether the streaming terminates with an injected text query. @@ -982,6 +984,11 @@ class CloudConversationDebuggingInfo(proto.Message): number=15, message=duration_pb2.Duration, ) + endpointing_timeout: duration_pb2.Duration = proto.Field( + proto.MESSAGE, + number=19, + message=duration_pb2.Duration, + ) is_input_text: bool = proto.Field( proto.BOOL, number=16, diff --git a/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_async.py b/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_async.py index 6f67a5bf..fad4a040 100644 --- a/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_async.py +++ b/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_async.py @@ -42,7 +42,7 @@ async def sample_clear_suggestion_feature_config(): request = dialogflow_v2beta1.ClearSuggestionFeatureConfigRequest( conversation_profile="conversation_profile_value", participant_role="END_USER", - suggestion_feature_type="CONVERSATION_SUMMARIZATION", + suggestion_feature_type="KNOWLEDGE_SEARCH", ) # Make the request diff --git a/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_sync.py b/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_sync.py index 547e5fc2..a59c725d 100644 --- a/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_sync.py +++ b/samples/generated_samples/dialogflow_v2beta1_generated_conversation_profiles_clear_suggestion_feature_config_sync.py @@ -42,7 +42,7 @@ def sample_clear_suggestion_feature_config(): request = dialogflow_v2beta1.ClearSuggestionFeatureConfigRequest( conversation_profile="conversation_profile_value", participant_role="END_USER", - suggestion_feature_type="CONVERSATION_SUMMARIZATION", + suggestion_feature_type="KNOWLEDGE_SEARCH", ) # Make the request diff --git a/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_async.py b/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_async.py new file mode 100644 index 00000000..33534cc1 --- /dev/null +++ b/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_async.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generated code. DO NOT EDIT! +# +# Snippet for SearchKnowledge +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-dialogflow + + +# [START dialogflow_v2beta1_generated_Conversations_SearchKnowledge_async] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import dialogflow_v2beta1 + + +async def sample_search_knowledge(): + # Create a client + client = dialogflow_v2beta1.ConversationsAsyncClient() + + # Initialize request argument(s) + request = dialogflow_v2beta1.SearchKnowledgeRequest( + conversation_profile="conversation_profile_value", + ) + + # Make the request + response = await client.search_knowledge(request=request) + + # Handle the response + print(response) + +# [END dialogflow_v2beta1_generated_Conversations_SearchKnowledge_async] diff --git a/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_sync.py b/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_sync.py new file mode 100644 index 00000000..9608390a --- /dev/null +++ b/samples/generated_samples/dialogflow_v2beta1_generated_conversations_search_knowledge_sync.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Generated code. DO NOT EDIT! +# +# Snippet for SearchKnowledge +# NOTE: This snippet has been automatically generated for illustrative purposes only. +# It may require modifications to work in your environment. + +# To install the latest published package dependency, execute the following: +# python3 -m pip install google-cloud-dialogflow + + +# [START dialogflow_v2beta1_generated_Conversations_SearchKnowledge_sync] +# This snippet has been automatically generated and should be regarded as a +# code template only. +# It will require modifications to work: +# - It may require correct/in-range values for request initialization. +# - It may require specifying regional endpoints when creating the service +# client as shown in: +# https://googleapis.dev/python/google-api-core/latest/client_options.html +from google.cloud import dialogflow_v2beta1 + + +def sample_search_knowledge(): + # Create a client + client = dialogflow_v2beta1.ConversationsClient() + + # Initialize request argument(s) + request = dialogflow_v2beta1.SearchKnowledgeRequest( + conversation_profile="conversation_profile_value", + ) + + # Make the request + response = client.search_knowledge(request=request) + + # Handle the response + print(response) + +# [END dialogflow_v2beta1_generated_Conversations_SearchKnowledge_sync] diff --git a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.v2beta1.json b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.v2beta1.json index edb413c0..11feb40f 100644 --- a/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.v2beta1.json +++ b/samples/generated_samples/snippet_metadata_google.cloud.dialogflow.v2beta1.json @@ -5179,6 +5179,159 @@ ], "title": "dialogflow_v2beta1_generated_conversations_list_messages_sync.py" }, + { + "canonical": true, + "clientMethod": { + "async": true, + "client": { + "fullName": "google.cloud.dialogflow_v2beta1.ConversationsAsyncClient", + "shortName": "ConversationsAsyncClient" + }, + "fullName": "google.cloud.dialogflow_v2beta1.ConversationsAsyncClient.search_knowledge", + "method": { + "fullName": "google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge", + "service": { + "fullName": "google.cloud.dialogflow.v2beta1.Conversations", + "shortName": "Conversations" + }, + "shortName": "SearchKnowledge" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.dialogflow_v2beta1.types.SearchKnowledgeRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, str]" + } + ], + "resultType": "google.cloud.dialogflow_v2beta1.types.SearchKnowledgeResponse", + "shortName": "search_knowledge" + }, + "description": "Sample for SearchKnowledge", + "file": "dialogflow_v2beta1_generated_conversations_search_knowledge_async.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "dialogflow_v2beta1_generated_Conversations_SearchKnowledge_async", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "dialogflow_v2beta1_generated_conversations_search_knowledge_async.py" + }, + { + "canonical": true, + "clientMethod": { + "client": { + "fullName": "google.cloud.dialogflow_v2beta1.ConversationsClient", + "shortName": "ConversationsClient" + }, + "fullName": "google.cloud.dialogflow_v2beta1.ConversationsClient.search_knowledge", + "method": { + "fullName": "google.cloud.dialogflow.v2beta1.Conversations.SearchKnowledge", + "service": { + "fullName": "google.cloud.dialogflow.v2beta1.Conversations", + "shortName": "Conversations" + }, + "shortName": "SearchKnowledge" + }, + "parameters": [ + { + "name": "request", + "type": "google.cloud.dialogflow_v2beta1.types.SearchKnowledgeRequest" + }, + { + "name": "retry", + "type": "google.api_core.retry.Retry" + }, + { + "name": "timeout", + "type": "float" + }, + { + "name": "metadata", + "type": "Sequence[Tuple[str, str]" + } + ], + "resultType": "google.cloud.dialogflow_v2beta1.types.SearchKnowledgeResponse", + "shortName": "search_knowledge" + }, + "description": "Sample for SearchKnowledge", + "file": "dialogflow_v2beta1_generated_conversations_search_knowledge_sync.py", + "language": "PYTHON", + "origin": "API_DEFINITION", + "regionTag": "dialogflow_v2beta1_generated_Conversations_SearchKnowledge_sync", + "segments": [ + { + "end": 51, + "start": 27, + "type": "FULL" + }, + { + "end": 51, + "start": 27, + "type": "SHORT" + }, + { + "end": 40, + "start": 38, + "type": "CLIENT_INITIALIZATION" + }, + { + "end": 45, + "start": 41, + "type": "REQUEST_INITIALIZATION" + }, + { + "end": 48, + "start": 46, + "type": "REQUEST_EXECUTION" + }, + { + "end": 52, + "start": 49, + "type": "RESPONSE_HANDLING" + } + ], + "title": "dialogflow_v2beta1_generated_conversations_search_knowledge_sync.py" + }, { "canonical": true, "clientMethod": { diff --git a/scripts/fixup_dialogflow_v2beta1_keywords.py b/scripts/fixup_dialogflow_v2beta1_keywords.py index 5f46862c..26a9e1d9 100644 --- a/scripts/fixup_dialogflow_v2beta1_keywords.py +++ b/scripts/fixup_dialogflow_v2beta1_keywords.py @@ -111,6 +111,7 @@ class dialogflowCallTransformer(cst.CSTTransformer): 'reload_document': ('name', 'gcs_source', 'import_gcs_custom_metadata', ), 'restore_agent': ('parent', 'agent_uri', 'agent_content', ), 'search_agents': ('parent', 'page_size', 'page_token', ), + 'search_knowledge': ('query', 'conversation_profile', 'parent', 'session_id', 'conversation', 'latest_message', ), 'set_agent': ('agent', 'update_mask', ), 'set_suggestion_feature_config': ('conversation_profile', 'participant_role', 'suggestion_feature_config', ), 'streaming_analyze_content': ('participant', 'audio_config', 'text_config', 'reply_audio_config', 'input_audio', 'input_text', 'input_dtmf', 'query_params', 'assist_query_params', 'cx_parameters', 'cx_current_page', 'enable_partial_automated_agent_reply', 'enable_debugging_info', ), diff --git a/tests/unit/gapic/dialogflow_v2beta1/test_answer_records.py b/tests/unit/gapic/dialogflow_v2beta1/test_answer_records.py index 1dff68fc..b87da176 100644 --- a/tests/unit/gapic/dialogflow_v2beta1/test_answer_records.py +++ b/tests/unit/gapic/dialogflow_v2beta1/test_answer_records.py @@ -1965,6 +1965,10 @@ def test_update_answer_record_rest(request_type): "submit_timestamp": {}, "summary_text": "summary_text_value", }, + "knowledge_search_feedback": { + "answer_copied": True, + "clicked_uris": ["clicked_uris_value1", "clicked_uris_value2"], + }, }, "clicked": True, "click_time": {}, @@ -2446,6 +2450,10 @@ def test_update_answer_record_rest_bad_request( "submit_timestamp": {}, "summary_text": "summary_text_value", }, + "knowledge_search_feedback": { + "answer_copied": True, + "clicked_uris": ["clicked_uris_value1", "clicked_uris_value2"], + }, }, "clicked": True, "click_time": {}, diff --git a/tests/unit/gapic/dialogflow_v2beta1/test_conversation_profiles.py b/tests/unit/gapic/dialogflow_v2beta1/test_conversation_profiles.py index a1197b7a..65e38aca 100644 --- a/tests/unit/gapic/dialogflow_v2beta1/test_conversation_profiles.py +++ b/tests/unit/gapic/dialogflow_v2beta1/test_conversation_profiles.py @@ -3490,6 +3490,7 @@ def test_create_conversation_profile_rest(request_type): { "suggestion_feature": {"type_": 1}, "enable_event_based_suggestion": True, + "disable_agent_query_logging": True, "suggestion_trigger_settings": { "no_small_talk": True, "only_end_user": True, @@ -3543,7 +3544,11 @@ def test_create_conversation_profile_rest(request_type): "notification_config": {}, "logging_config": {"enable_stackdriver_logging": True}, "new_message_event_notification_config": {}, - "stt_config": {"speech_model_variant": 1, "model": "model_value"}, + "stt_config": { + "speech_model_variant": 1, + "model": "model_value", + "use_timeout_based_endpointing": True, + }, "language_code": "language_code_value", "time_zone": "time_zone_value", "security_settings": "security_settings_value", @@ -3775,6 +3780,7 @@ def test_create_conversation_profile_rest_bad_request( { "suggestion_feature": {"type_": 1}, "enable_event_based_suggestion": True, + "disable_agent_query_logging": True, "suggestion_trigger_settings": { "no_small_talk": True, "only_end_user": True, @@ -3828,7 +3834,11 @@ def test_create_conversation_profile_rest_bad_request( "notification_config": {}, "logging_config": {"enable_stackdriver_logging": True}, "new_message_event_notification_config": {}, - "stt_config": {"speech_model_variant": 1, "model": "model_value"}, + "stt_config": { + "speech_model_variant": 1, + "model": "model_value", + "use_timeout_based_endpointing": True, + }, "language_code": "language_code_value", "time_zone": "time_zone_value", "security_settings": "security_settings_value", @@ -3960,6 +3970,7 @@ def test_update_conversation_profile_rest(request_type): { "suggestion_feature": {"type_": 1}, "enable_event_based_suggestion": True, + "disable_agent_query_logging": True, "suggestion_trigger_settings": { "no_small_talk": True, "only_end_user": True, @@ -4013,7 +4024,11 @@ def test_update_conversation_profile_rest(request_type): "notification_config": {}, "logging_config": {"enable_stackdriver_logging": True}, "new_message_event_notification_config": {}, - "stt_config": {"speech_model_variant": 1, "model": "model_value"}, + "stt_config": { + "speech_model_variant": 1, + "model": "model_value", + "use_timeout_based_endpointing": True, + }, "language_code": "language_code_value", "time_zone": "time_zone_value", "security_settings": "security_settings_value", @@ -4246,6 +4261,7 @@ def test_update_conversation_profile_rest_bad_request( { "suggestion_feature": {"type_": 1}, "enable_event_based_suggestion": True, + "disable_agent_query_logging": True, "suggestion_trigger_settings": { "no_small_talk": True, "only_end_user": True, @@ -4299,7 +4315,11 @@ def test_update_conversation_profile_rest_bad_request( "notification_config": {}, "logging_config": {"enable_stackdriver_logging": True}, "new_message_event_notification_config": {}, - "stt_config": {"speech_model_variant": 1, "model": "model_value"}, + "stt_config": { + "speech_model_variant": 1, + "model": "model_value", + "use_timeout_based_endpointing": True, + }, "language_code": "language_code_value", "time_zone": "time_zone_value", "security_settings": "security_settings_value", diff --git a/tests/unit/gapic/dialogflow_v2beta1/test_conversations.py b/tests/unit/gapic/dialogflow_v2beta1/test_conversations.py index 9ccd8e23..ea8ec71f 100644 --- a/tests/unit/gapic/dialogflow_v2beta1/test_conversations.py +++ b/tests/unit/gapic/dialogflow_v2beta1/test_conversations.py @@ -3044,6 +3044,150 @@ async def test_generate_stateless_summary_field_headers_async(): ) in kw["metadata"] +@pytest.mark.parametrize( + "request_type", + [ + conversation.SearchKnowledgeRequest, + dict, + ], +) +def test_search_knowledge(request_type, transport: str = "grpc"): + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.search_knowledge), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = conversation.SearchKnowledgeResponse() + response = client.search_knowledge(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == conversation.SearchKnowledgeRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, conversation.SearchKnowledgeResponse) + + +def test_search_knowledge_empty_call(): + # This test is a coverage failsafe to make sure that totally empty calls, + # i.e. request == None and no flattened fields passed, work. + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="grpc", + ) + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.search_knowledge), "__call__") as call: + client.search_knowledge() + call.assert_called() + _, args, _ = call.mock_calls[0] + assert args[0] == conversation.SearchKnowledgeRequest() + + +@pytest.mark.asyncio +async def test_search_knowledge_async( + transport: str = "grpc_asyncio", request_type=conversation.SearchKnowledgeRequest +): + client = ConversationsAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # Everything is optional in proto3 as far as the runtime is concerned, + # and we are mocking out the actual API, so just send an empty request. + request = request_type() + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.search_knowledge), "__call__") as call: + # Designate an appropriate return value for the call. + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + conversation.SearchKnowledgeResponse() + ) + response = await client.search_knowledge(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == conversation.SearchKnowledgeRequest() + + # Establish that the response is the type that we expect. + assert isinstance(response, conversation.SearchKnowledgeResponse) + + +@pytest.mark.asyncio +async def test_search_knowledge_async_from_dict(): + await test_search_knowledge_async(request_type=dict) + + +def test_search_knowledge_field_headers(): + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = conversation.SearchKnowledgeRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.search_knowledge), "__call__") as call: + call.return_value = conversation.SearchKnowledgeResponse() + client.search_knowledge(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) == 1 + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "parent=parent_value", + ) in kw["metadata"] + + +@pytest.mark.asyncio +async def test_search_knowledge_field_headers_async(): + client = ConversationsAsyncClient( + credentials=ga_credentials.AnonymousCredentials(), + ) + + # Any value that is part of the HTTP/1.1 URI should be sent as + # a field header. Set these to a non-empty value. + request = conversation.SearchKnowledgeRequest() + + request.parent = "parent_value" + + # Mock the actual call within the gRPC stub, and fake the request. + with mock.patch.object(type(client.transport.search_knowledge), "__call__") as call: + call.return_value = grpc_helpers_async.FakeUnaryUnaryCall( + conversation.SearchKnowledgeResponse() + ) + await client.search_knowledge(request) + + # Establish that the underlying gRPC stub method was called. + assert len(call.mock_calls) + _, args, _ = call.mock_calls[0] + assert args[0] == request + + # Establish that the field header was sent. + _, _, kw = call.mock_calls[0] + assert ( + "x-goog-request-params", + "parent=parent_value", + ) in kw["metadata"] + + @pytest.mark.parametrize( "request_type", [ @@ -5369,6 +5513,224 @@ def test_generate_stateless_summary_rest_error(): ) +@pytest.mark.parametrize( + "request_type", + [ + conversation.SearchKnowledgeRequest, + dict, + ], +) +def test_search_knowledge_rest(request_type): + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a response. + with mock.patch.object(type(client.transport._session), "request") as req: + # Designate an appropriate value for the returned response. + return_value = conversation.SearchKnowledgeResponse() + + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 200 + pb_return_value = conversation.SearchKnowledgeResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + response = client.search_knowledge(request) + + # Establish that the response is the type that we expect. + assert isinstance(response, conversation.SearchKnowledgeResponse) + + +def test_search_knowledge_rest_required_fields( + request_type=conversation.SearchKnowledgeRequest, +): + transport_class = transports.ConversationsRestTransport + + request_init = {} + request_init["conversation_profile"] = "" + request = request_type(**request_init) + pb_request = request_type.pb(request) + jsonified_request = json.loads( + json_format.MessageToJson( + pb_request, + including_default_value_fields=False, + use_integers_for_enums=False, + ) + ) + + # verify fields with default values are dropped + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).search_knowledge._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with default values are now present + + jsonified_request["conversationProfile"] = "conversation_profile_value" + + unset_fields = transport_class( + credentials=ga_credentials.AnonymousCredentials() + ).search_knowledge._get_unset_required_fields(jsonified_request) + jsonified_request.update(unset_fields) + + # verify required fields with non-default values are left alone + assert "conversationProfile" in jsonified_request + assert jsonified_request["conversationProfile"] == "conversation_profile_value" + + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport="rest", + ) + request = request_type(**request_init) + + # Designate an appropriate value for the returned response. + return_value = conversation.SearchKnowledgeResponse() + # Mock the http request call within the method and fake a response. + with mock.patch.object(Session, "request") as req: + # We need to mock transcode() because providing default values + # for required fields will fail the real version if the http_options + # expect actual values for those fields. + with mock.patch.object(path_template, "transcode") as transcode: + # A uri without fields and an empty body will force all the + # request fields to show up in the query_params. + pb_request = request_type.pb(request) + transcode_result = { + "uri": "v1/sample_method", + "method": "post", + "query_params": pb_request, + } + transcode_result["body"] = pb_request + transcode.return_value = transcode_result + + response_value = Response() + response_value.status_code = 200 + + pb_return_value = conversation.SearchKnowledgeResponse.pb(return_value) + json_return_value = json_format.MessageToJson(pb_return_value) + + response_value._content = json_return_value.encode("UTF-8") + req.return_value = response_value + + response = client.search_knowledge(request) + + expected_params = [("$alt", "json;enum-encoding=int")] + actual_params = req.call_args.kwargs["params"] + assert expected_params == actual_params + + +def test_search_knowledge_rest_unset_required_fields(): + transport = transports.ConversationsRestTransport( + credentials=ga_credentials.AnonymousCredentials + ) + + unset_fields = transport.search_knowledge._get_unset_required_fields({}) + assert set(unset_fields) == ( + set(()) + & set( + ( + "query", + "conversationProfile", + ) + ) + ) + + +@pytest.mark.parametrize("null_interceptor", [True, False]) +def test_search_knowledge_rest_interceptors(null_interceptor): + transport = transports.ConversationsRestTransport( + credentials=ga_credentials.AnonymousCredentials(), + interceptor=None + if null_interceptor + else transports.ConversationsRestInterceptor(), + ) + client = ConversationsClient(transport=transport) + with mock.patch.object( + type(client.transport._session), "request" + ) as req, mock.patch.object( + path_template, "transcode" + ) as transcode, mock.patch.object( + transports.ConversationsRestInterceptor, "post_search_knowledge" + ) as post, mock.patch.object( + transports.ConversationsRestInterceptor, "pre_search_knowledge" + ) as pre: + pre.assert_not_called() + post.assert_not_called() + pb_message = conversation.SearchKnowledgeRequest.pb( + conversation.SearchKnowledgeRequest() + ) + transcode.return_value = { + "method": "post", + "uri": "my_uri", + "body": pb_message, + "query_params": pb_message, + } + + req.return_value = Response() + req.return_value.status_code = 200 + req.return_value.request = PreparedRequest() + req.return_value._content = conversation.SearchKnowledgeResponse.to_json( + conversation.SearchKnowledgeResponse() + ) + + request = conversation.SearchKnowledgeRequest() + metadata = [ + ("key", "val"), + ("cephalopod", "squid"), + ] + pre.return_value = request, metadata + post.return_value = conversation.SearchKnowledgeResponse() + + client.search_knowledge( + request, + metadata=[ + ("key", "val"), + ("cephalopod", "squid"), + ], + ) + + pre.assert_called_once() + post.assert_called_once() + + +def test_search_knowledge_rest_bad_request( + transport: str = "rest", request_type=conversation.SearchKnowledgeRequest +): + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), + transport=transport, + ) + + # send a request that will satisfy transcoding + request_init = {"parent": "projects/sample1"} + request = request_type(**request_init) + + # Mock the http request call within the method and fake a BadRequest error. + with mock.patch.object(Session, "request") as req, pytest.raises( + core_exceptions.BadRequest + ): + # Wrap the value into a proper Response obj + response_value = Response() + response_value.status_code = 400 + response_value.request = Request() + req.return_value = response_value + client.search_knowledge(request) + + +def test_search_knowledge_rest_error(): + client = ConversationsClient( + credentials=ga_credentials.AnonymousCredentials(), transport="rest" + ) + + def test_credentials_transport_error(): # It is an error to provide credentials and a transport instance. transport = transports.ConversationsGrpcTransport( @@ -5516,6 +5878,7 @@ def test_conversations_base_transport(): "list_messages", "suggest_conversation_summary", "generate_stateless_summary", + "search_knowledge", "get_location", "list_locations", "get_operation", @@ -5815,6 +6178,9 @@ def test_conversations_client_transport_session_collision(transport_name): session1 = client1.transport.generate_stateless_summary._session session2 = client2.transport.generate_stateless_summary._session assert session1 != session2 + session1 = client1.transport.search_knowledge._session + session2 = client2.transport.search_knowledge._session + assert session1 != session2 def test_conversations_grpc_transport_channel():