From 9d006b7042c106784aac91752843874fea617a36 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 24 Feb 2025 11:24:34 +0000 Subject: [PATCH 1/9] Conversation API support (sync) Signed-off-by: Elena Kolevska --- dapr/clients/grpc/_request.py | 10 +++ dapr/clients/grpc/_response.py | 17 +++++ dapr/clients/grpc/client.py | 62 ++++++++++++++++++ examples/conversation/README.md | 35 ++++++++++ .../config/conversation-echo.yaml | 7 ++ examples/conversation/conversation.py | 33 ++++++++++ tests/clients/fake_dapr_server.py | 12 ++++ tests/clients/test_dapr_grpc_client.py | 65 ++++++++++++++++++- 8 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 examples/conversation/README.md create mode 100644 examples/conversation/config/conversation-echo.yaml create mode 100644 examples/conversation/conversation.py diff --git a/dapr/clients/grpc/_request.py b/dapr/clients/grpc/_request.py index 9ad291cff..9bfa8b397 100644 --- a/dapr/clients/grpc/_request.py +++ b/dapr/clients/grpc/_request.py @@ -15,6 +15,7 @@ import io from enum import Enum +from dataclasses import dataclass from typing import Dict, Optional, Union from google.protobuf.any_pb2 import Any as GrpcAny @@ -418,3 +419,12 @@ def __next__(self): self.seq += 1 return request_proto + + +@dataclass +class ConversationInput: + """A single input message for the conversation.""" + + message: str + role: Optional[str] = None + scrub_pii: Optional[bool] = None diff --git a/dapr/clients/grpc/_response.py b/dapr/clients/grpc/_response.py index 2beb1a2f5..6d6ee92a2 100644 --- a/dapr/clients/grpc/_response.py +++ b/dapr/clients/grpc/_response.py @@ -18,6 +18,7 @@ import contextlib import json import threading +from dataclasses import dataclass, field from datetime import datetime from enum import Enum from typing import ( @@ -1070,3 +1071,19 @@ class EncryptResponse(CryptoResponse[TCryptoResponse]): class DecryptResponse(CryptoResponse[TCryptoResponse]): ... + + +@dataclass +class ConversationResult: + """Result from a single conversation input.""" + + result: str + parameters: Dict[str, GrpcAny] = field(default_factory=dict) + + +@dataclass +class ConversationResponse: + """Response from the conversation API.""" + + context_id: Optional[str] + outputs: List[ConversationResult] diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index f7c7b8504..f457d9ab7 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -27,6 +27,7 @@ from datetime import datetime from google.protobuf.message import Message as GrpcMessage from google.protobuf.empty_pb2 import Empty as GrpcEmpty +from google.protobuf.any_pb2 import Any as GrpcAny import grpc # type: ignore from grpc import ( # type: ignore @@ -64,6 +65,7 @@ TransactionalStateOperation, EncryptRequestIterator, DecryptRequestIterator, + ConversationInput, ) from dapr.clients.grpc._response import ( BindingResponse, @@ -88,6 +90,8 @@ EncryptResponse, DecryptResponse, TopicEventResponse, + ConversationResponse, + ConversationResult, ) @@ -1713,6 +1717,64 @@ def purge_workflow(self, instance_id: str, workflow_component: str) -> DaprRespo except RpcError as err: raise DaprInternalError(err.details()) + def converse_alpha1( + self, + name: str, + inputs: List[ConversationInput], + *, + # Force remaining args to be keyword-only + context_id: Optional[str] = None, + parameters: Optional[Dict[str, GrpcAny]] = None, + metadata: Optional[Dict[str, str]] = None, + scrub_pii: Optional[bool] = None, + temperature: Optional[float] = None, + ) -> ConversationResponse: + """Invoke an LLM using the conversation API (Alpha). + + Args: + name: Name of the LLM component to invoke + inputs: List of conversation inputs + context_id: Optional ID for continuing an existing chat + parameters: Optional custom parameters for the request + metadata: Optional metadata for the component + scrub_pii: Optional flag to scrub PII from outputs + temperature: Optional temperature setting for the LLM to optimize for creativity or predictability + + Returns: + ConversationResponse containing the conversation results + + Raises: + DaprInternalError: If the Dapr runtime returns an error + """ + + inputs_pb = [ + api_v1.ConversationInput(message=inp.message, role=inp.role, scrubPII=inp.scrub_pii) + for inp in inputs + ] + + request = api_v1.ConversationRequest( + name=name, + inputs=inputs_pb, + contextID=context_id, + parameters=parameters or {}, + metadata=metadata or {}, + scrubPII=scrub_pii, + temperature=temperature, + ) + + try: + response, call = self.retry_policy.run_rpc(self._stub.ConverseAlpha1.with_call, request) + + outputs = [ + ConversationResult(result=output.result, parameters=output.parameters) + for output in response.outputs + ] + + return ConversationResponse(context_id=response.contextID, outputs=outputs) + + except Exception as ex: + raise DaprInternalError(f'Error invoking conversation API: {str(ex)}') + def wait(self, timeout_s: float): """Waits for sidecar to be available within the timeout. diff --git a/examples/conversation/README.md b/examples/conversation/README.md new file mode 100644 index 000000000..787ac58ec --- /dev/null +++ b/examples/conversation/README.md @@ -0,0 +1,35 @@ +# Example - Conversation API + +## Step + +### Prepare + +- Dapr installed + +### Run Conversation Example + + + +```bash +dapr run --app-id conversation \ + --log-level debug \ + --resources-path ./config \ + -- python3 conversation.py +``` + + + +## Result + +``` + - '== APP == Result: What's Dapr?' + - '== APP == Result: Give a brief overview.' +``` \ No newline at end of file diff --git a/examples/conversation/config/conversation-echo.yaml b/examples/conversation/config/conversation-echo.yaml new file mode 100644 index 000000000..9a8b3072d --- /dev/null +++ b/examples/conversation/config/conversation-echo.yaml @@ -0,0 +1,7 @@ +apiVersion: dapr.io/v1alpha1 +kind: Component +metadata: + name: echo +spec: + type: conversation.echo + version: v1 \ No newline at end of file diff --git a/examples/conversation/conversation.py b/examples/conversation/conversation.py new file mode 100644 index 000000000..e2eb1b2a1 --- /dev/null +++ b/examples/conversation/conversation.py @@ -0,0 +1,33 @@ +# ------------------------------------------------------------ +# Copyright 2025 The Dapr Authors +# 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. +# ------------------------------------------------------------ +from dapr.clients import DaprClient +from dapr.clients.grpc._request import ConversationInput + +with DaprClient() as d: + inputs = [ + ConversationInput(message="What's Dapr?", role='user', scrub_pii=True), + ConversationInput(message='Give a brief overview.', role='user', scrub_pii=True), + ] + + metadata = { + 'model': 'foo', + 'key': 'authKey', + 'cacheTTL': '10m', + } + + response = d.converse_alpha1( + name='echo', inputs=inputs, temperature=0.7, context_id='chat-123', metadata=metadata + ) + + for output in response.outputs: + print(f'Result: {output.result}') diff --git a/tests/clients/fake_dapr_server.py b/tests/clients/fake_dapr_server.py index 9ae39aa1c..d4b2e31d6 100644 --- a/tests/clients/fake_dapr_server.py +++ b/tests/clients/fake_dapr_server.py @@ -524,6 +524,18 @@ def GetMetadata(self, request, context): extended_metadata=self.metadata, ) + def ConverseAlpha1(self, request, context): + """Mock implementation of the ConverseAlpha1 endpoint.""" + self.check_for_exception(context) + + # Echo back the input messages as outputs + outputs = [] + for input in request.inputs: + result = f'Response to: {input.message}' + outputs.append(api_v1.ConversationResult(result=result, parameters={})) + + return api_v1.ConversationResponse(contextID=request.contextID, outputs=outputs) + def SetMetadata(self, request: SetMetadataRequest, context): self.metadata[request.key] = request.value return empty_pb2.Empty() diff --git a/tests/clients/test_dapr_grpc_client.py b/tests/clients/test_dapr_grpc_client.py index f0644ec7f..6f65e267e 100644 --- a/tests/clients/test_dapr_grpc_client.py +++ b/tests/clients/test_dapr_grpc_client.py @@ -25,7 +25,7 @@ from google.rpc import status_pb2, code_pb2 -from dapr.clients.exceptions import DaprGrpcError +from dapr.clients.exceptions import DaprGrpcError, DaprInternalError from dapr.clients.grpc.client import DaprGrpcClient from dapr.clients import DaprClient from dapr.clients.grpc.subscription import StreamInactiveError @@ -33,7 +33,11 @@ from .fake_dapr_server import FakeDaprSidecar from dapr.conf import settings from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import TransactionalStateOperation, TransactionOperationType +from dapr.clients.grpc._request import ( + TransactionalStateOperation, + TransactionOperationType, + ConversationInput, +) from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions from dapr.clients.grpc._response import ( @@ -1181,6 +1185,63 @@ def test_decrypt_file_data_read_chunks(self): self.assertEqual(resp.read(5), b'hello') self.assertEqual(resp.read(5), b' dapr') + def test_converse_alpha1_basic(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + inputs = [ + ConversationInput(message='Hello', role='user'), + ConversationInput(message='How are you?', role='user'), + ] + + response = dapr.converse_alpha1(name='test-llm', inputs=inputs) + + # Check response structure + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 2) + self.assertEqual(response.outputs[0].result, 'Response to: Hello') + self.assertEqual(response.outputs[1].result, 'Response to: How are you?') + + def test_converse_alpha1_with_options(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + inputs = [ConversationInput(message='Hello', role='user', scrub_pii=True)] + + response = dapr.converse_alpha1( + name='test-llm', + inputs=inputs, + context_id='chat-123', + temperature=0.7, + scrub_pii=True, + metadata={'key': 'value'}, + ) + + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 1) + self.assertEqual(response.outputs[0].result, 'Response to: Hello') + + def test_converse_alpha1_error_handling(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + # Setup server to raise an exception + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message='Invalid argument') + ) + + inputs = [ConversationInput(message='Hello', role='user')] + + with self.assertRaises(DaprInternalError) as context: + dapr.converse_alpha1(name='test-llm', inputs=inputs) + self.assertTrue('Invalid argument' in str(context.exception)) + + def test_converse_alpha1_empty_inputs(self): + dapr = DaprGrpcClient(f'{self.scheme}localhost:{self.grpc_port}') + + # Test with empty inputs list + response = dapr.converse_alpha1(name='test-llm', inputs=[]) + + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 0) + if __name__ == '__main__': unittest.main() From d40751b24bb85f87a6b3d0ac5ee16fa977b41a86 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 24 Feb 2025 11:46:06 +0000 Subject: [PATCH 2/9] async conversation api support Signed-off-by: Elena Kolevska --- dapr/aio/clients/grpc/client.py | 63 ++++++++++++++++++++ examples/conversation/conversation.py | 2 +- tests/clients/test_dapr_grpc_client_async.py | 56 ++++++++++++++++- 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/dapr/aio/clients/grpc/client.py b/dapr/aio/clients/grpc/client.py index 7bcf4a136..c568b6db7 100644 --- a/dapr/aio/clients/grpc/client.py +++ b/dapr/aio/clients/grpc/client.py @@ -29,6 +29,7 @@ from google.protobuf.message import Message as GrpcMessage from google.protobuf.empty_pb2 import Empty as GrpcEmpty +from google.protobuf.any_pb2 import Any as GrpcAny import grpc.aio # type: ignore from grpc.aio import ( # type: ignore @@ -75,9 +76,12 @@ InvokeMethodRequest, BindingRequest, TransactionalStateOperation, + ConversationInput, ) from dapr.clients.grpc._response import ( BindingResponse, + ConversationResponse, + ConversationResult, DaprResponse, GetSecretResponse, GetBulkSecretResponse, @@ -1711,6 +1715,65 @@ async def purge_workflow(self, instance_id: str, workflow_component: str) -> Dap except grpc.aio.AioRpcError as err: raise DaprInternalError(err.details()) + async def converse_alpha1( + self, + name: str, + inputs: List[ConversationInput], + *, + # Force remaining args to be keyword-only + context_id: Optional[str] = None, + parameters: Optional[Dict[str, GrpcAny]] = None, + metadata: Optional[Dict[str, str]] = None, + scrub_pii: Optional[bool] = None, + temperature: Optional[float] = None, + ) -> ConversationResponse: + """Invoke an LLM using the conversation API (Alpha). + + Args: + name: Name of the LLM component to invoke + inputs: List of conversation inputs + context_id: Optional ID for continuing an existing chat + parameters: Optional custom parameters for the request + metadata: Optional metadata for the component + scrub_pii: Optional flag to scrub PII from outputs + temperature: Optional temperature setting (0.0 to 1.0) where + lower values give more deterministic responses and + higher values enable more creative responses + + Returns: + ConversationResponse containing the conversation results + + Raises: + DaprInternalError: If the Dapr runtime returns an error + """ + inputs_pb = [ + api_v1.ConversationInput(message=inp.message, role=inp.role, scrubPII=inp.scrub_pii) + for inp in inputs + ] + + request = api_v1.ConversationRequest( + name=name, + inputs=inputs_pb, + contextID=context_id, + parameters=parameters or {}, + metadata=metadata or {}, + scrubPII=scrub_pii, + temperature=temperature, + ) + + try: + response = await self._stub.ConverseAlpha1(request) + + outputs = [ + ConversationResult(result=output.result, parameters=output.parameters) + for output in response.outputs + ] + + return ConversationResponse(context_id=response.contextID, outputs=outputs) + + except Exception as ex: + raise DaprInternalError(f'Error invoking conversation API: {str(ex)}') + async def wait(self, timeout_s: float): """Waits for sidecar to be available within the timeout. diff --git a/examples/conversation/conversation.py b/examples/conversation/conversation.py index e2eb1b2a1..d1141ca32 100644 --- a/examples/conversation/conversation.py +++ b/examples/conversation/conversation.py @@ -30,4 +30,4 @@ ) for output in response.outputs: - print(f'Result: {output.result}') + print(f'Result: {output.result}') \ No newline at end of file diff --git a/tests/clients/test_dapr_grpc_client_async.py b/tests/clients/test_dapr_grpc_client_async.py index f15a2d1af..8c8a147d3 100644 --- a/tests/clients/test_dapr_grpc_client_async.py +++ b/tests/clients/test_dapr_grpc_client_async.py @@ -23,13 +23,13 @@ from dapr.aio.clients.grpc.client import DaprGrpcClientAsync from dapr.aio.clients import DaprClient -from dapr.clients.exceptions import DaprGrpcError +from dapr.clients.exceptions import DaprGrpcError, DaprInternalError from dapr.common.pubsub.subscription import StreamInactiveError from dapr.proto import common_v1 from .fake_dapr_server import FakeDaprSidecar from dapr.conf import settings from dapr.clients.grpc._helpers import to_bytes -from dapr.clients.grpc._request import TransactionalStateOperation +from dapr.clients.grpc._request import TransactionalStateOperation, ConversationInput from dapr.clients.grpc._state import StateOptions, Consistency, Concurrency, StateItem from dapr.clients.grpc._crypto import EncryptOptions, DecryptOptions from dapr.clients.grpc._response import ( @@ -1113,6 +1113,58 @@ async def test_decrypt_file_data_read_chunks(self): self.assertEqual(await resp.read(5), b'hello') self.assertEqual(await resp.read(5), b' dapr') + async def test_converse_alpha1_basic(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + inputs = [ConversationInput(message="Hello", role="user"), + ConversationInput(message="How are you?", role="user")] + + response = await dapr.converse_alpha1(name="test-llm", inputs=inputs) + + # Check response structure + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 2) + self.assertEqual(response.outputs[0].result, "Response to: Hello") + self.assertEqual(response.outputs[1].result, "Response to: How are you?") + await dapr.close() + + async def test_converse_alpha1_with_options(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + inputs = [ConversationInput(message="Hello", role="user", scrub_pii=True)] + + response = await dapr.converse_alpha1(name="test-llm", inputs=inputs, context_id="chat-123", + temperature=0.7, scrub_pii=True, metadata={"key": "value"}) + + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 1) + self.assertEqual(response.outputs[0].result, "Response to: Hello") + await dapr.close() + + async def test_converse_alpha1_error_handling(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + # Setup server to raise an exception + self._fake_dapr_server.raise_exception_on_next_call( + status_pb2.Status(code=code_pb2.INVALID_ARGUMENT, message="Invalid argument")) + + inputs = [ConversationInput(message="Hello", role="user")] + + with self.assertRaises(DaprInternalError) as context: + await dapr.converse_alpha1(name="test-llm", inputs=inputs) + self.assertTrue("Invalid argument" in str(context.exception)) + await dapr.close() + + async def test_converse_alpha1_empty_inputs(self): + dapr = DaprGrpcClientAsync(f'{self.scheme}localhost:{self.grpc_port}') + + # Test with empty inputs list + response = await dapr.converse_alpha1(name="test-llm", inputs=[]) + + self.assertIsNotNone(response) + self.assertEqual(len(response.outputs), 0) + await dapr.close() + if __name__ == '__main__': unittest.main() From 516bd9f9487f6d27816ed96cd02827e16c7e2b60 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 24 Feb 2025 14:50:11 +0000 Subject: [PATCH 3/9] Adds docs Signed-off-by: Elena Kolevska --- .../en/python-sdk-docs/python-client.md | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/daprdocs/content/en/python-sdk-docs/python-client.md b/daprdocs/content/en/python-sdk-docs/python-client.md index 43afdd0be..0ca4b5a62 100644 --- a/daprdocs/content/en/python-sdk-docs/python-client.md +++ b/daprdocs/content/en/python-sdk-docs/python-client.md @@ -431,6 +431,38 @@ if __name__ == '__main__': - For more information about pub/sub, visit [How-To: Publish & subscribe]({{< ref howto-publish-subscribe.md >}}). - Visit [Python SDK examples](https://github.com/dapr/python-sdk/tree/main/examples/pubsub-simple) for code samples and instructions to try out streaming pub/sub. +### Conversation (Alpha) + +{{% alert title="Note" color="primary" %}} +The Dapr Conversation API is currently in alpha. +{{% /alert %}} + +Since version 1.15 Dapr offers developers the capability to securely and reliably interact with Large Language Models (LLM) through the [Conversation API]({{< ref conversation-overview.md >}}). + +```python +from dapr.clients import DaprClient +from dapr.clients.grpc._request import ConversationInput + +with DaprClient() as d: + inputs = [ + ConversationInput(message="What's Dapr?", role='user', scrub_pii=True), + ConversationInput(message='Give a brief overview.', role='user', scrub_pii=True), + ] + + metadata = { + 'model': 'foo', + 'key': 'authKey', + 'cacheTTL': '10m', + } + + response = d.converse_alpha1( + name='echo', inputs=inputs, temperature=0.7, context_id='chat-123', metadata=metadata + ) + + for output in response.outputs: + print(f'Result: {output.result}') +``` + ### Interact with output bindings ```python From ae7448242fe49a902d83fa4a339fd792ef2b778f Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 24 Feb 2025 15:10:52 +0000 Subject: [PATCH 4/9] Ignore validation of x.com link that needs authentication Signed-off-by: Elena Kolevska --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index b6f7d0a77..3491d39a2 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,9 @@ [![GitHub issue custom search in repo](https://img.shields.io/github/issues-search/dapr/python-sdk?query=type%3Aissue%20is%3Aopen%20label%3A%22good%20first%20issue%22&label=Good%20first%20issues&style=flat&logo=github)](https://github.com/dapr/python-sdk/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) [![Discord](https://img.shields.io/discord/778680217417809931?label=Discord&style=flat&logo=discord)](http://bit.ly/dapr-discord) [![YouTube Channel Views](https://img.shields.io/youtube/channel/views/UCtpSQ9BLB_3EXdWAUQYwnRA?style=flat&label=YouTube%20views&logo=youtube)](https://youtube.com/@daprdev) + [![X (formerly Twitter) Follow](https://img.shields.io/twitter/follow/daprdev?logo=x&style=flat)](https://twitter.com/daprdev) + [Dapr](https://docs.dapr.io/concepts/overview/) is a portable, event-driven, serverless runtime for building distributed applications across cloud and edge. From a23adb9ab196c3a9c7b039decbf6339fb955c430 Mon Sep 17 00:00:00 2001 From: Elena Kolevska Date: Mon, 24 Feb 2025 15:40:07 +0000 Subject: [PATCH 5/9] Registers e2e test. Clean up. Signed-off-by: Elena Kolevska --- dapr/aio/clients/grpc/client.py | 9 +++------ dapr/clients/grpc/client.py | 5 ++--- examples/conversation/README.md | 1 - tox.ini | 1 + 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/dapr/aio/clients/grpc/client.py b/dapr/aio/clients/grpc/client.py index c568b6db7..6366821ce 100644 --- a/dapr/aio/clients/grpc/client.py +++ b/dapr/aio/clients/grpc/client.py @@ -1720,7 +1720,6 @@ async def converse_alpha1( name: str, inputs: List[ConversationInput], *, - # Force remaining args to be keyword-only context_id: Optional[str] = None, parameters: Optional[Dict[str, GrpcAny]] = None, metadata: Optional[Dict[str, str]] = None, @@ -1736,9 +1735,7 @@ async def converse_alpha1( parameters: Optional custom parameters for the request metadata: Optional metadata for the component scrub_pii: Optional flag to scrub PII from outputs - temperature: Optional temperature setting (0.0 to 1.0) where - lower values give more deterministic responses and - higher values enable more creative responses + temperature: Optional temperature setting for the LLM to optimize for creativity or predictability Returns: ConversationResponse containing the conversation results @@ -1771,8 +1768,8 @@ async def converse_alpha1( return ConversationResponse(context_id=response.contextID, outputs=outputs) - except Exception as ex: - raise DaprInternalError(f'Error invoking conversation API: {str(ex)}') + except Exception as e: + raise DaprInternalError(f'Error invoking conversation API: {e}') async def wait(self, timeout_s: float): """Waits for sidecar to be available within the timeout. diff --git a/dapr/clients/grpc/client.py b/dapr/clients/grpc/client.py index f457d9ab7..1e8f4664b 100644 --- a/dapr/clients/grpc/client.py +++ b/dapr/clients/grpc/client.py @@ -1722,7 +1722,6 @@ def converse_alpha1( name: str, inputs: List[ConversationInput], *, - # Force remaining args to be keyword-only context_id: Optional[str] = None, parameters: Optional[Dict[str, GrpcAny]] = None, metadata: Optional[Dict[str, str]] = None, @@ -1772,8 +1771,8 @@ def converse_alpha1( return ConversationResponse(context_id=response.contextID, outputs=outputs) - except Exception as ex: - raise DaprInternalError(f'Error invoking conversation API: {str(ex)}') + except Exception as e: + raise DaprInternalError(f'Error invoking conversation API: {e}') def wait(self, timeout_s: float): """Waits for sidecar to be available within the timeout. diff --git a/examples/conversation/README.md b/examples/conversation/README.md index 787ac58ec..c793dd4b5 100644 --- a/examples/conversation/README.md +++ b/examples/conversation/README.md @@ -10,7 +10,6 @@