Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fdff6cf
initial
filintod Jul 29, 2025
bbf381c
fixes after proto change upstream
filintod Jul 31, 2025
dbf6f56
minor name changes and cleanup unused function
filintod Aug 1, 2025
7a9a6cf
refactors, updates to readme, linting
filintod Aug 6, 2025
e412740
Merge branch 'main' into filinto/tool-calling
filintod Aug 7, 2025
08fdcab
Merge branch 'main' into filinto/tool-calling
filintod Aug 12, 2025
287d4b7
feedback
filintod Aug 12, 2025
b548c00
feedback, updates
filintod Aug 14, 2025
975df5d
fix import in examples
filintod Aug 14, 2025
6bbb0f5
cleanup, import, lint, more conversation helpers
filintod Aug 14, 2025
8697b8e
clarify README, minor test import changes, copyright
filintod Aug 14, 2025
5d16fb5
feedback DRY test_conversation file
filintod Aug 14, 2025
d0703ae
lint
filintod Aug 14, 2025
e10d2d8
move conversation classes in _response module to conversation module.
filintod Aug 16, 2025
3854bbd
minor readme change
filintod Aug 16, 2025
2018978
Update daprdocs/content/en/python-sdk-docs/python-client.md
filintod Aug 19, 2025
ee6729e
lint
filintod Aug 19, 2025
7126b2f
updates to fix issue with tool calling helper when dealing with class…
filintod Aug 21, 2025
fbc0195
coalesce conv helper tests, fix typing lint
filintod Aug 22, 2025
0d275e4
make indent line method doc more dev friendly
filintod Aug 25, 2025
a41a413
tackle some feedback, still missing unit tests
filintod Aug 25, 2025
5db8a56
add unit test to convert_value_to_struct
filintod Aug 25, 2025
6e3d2ba
more unit tests per feedback
filintod Aug 25, 2025
a4870a9
make async version of unit test conversation
filintod Aug 25, 2025
5bec64e
add some information how to run markdown tests with a different runtime
filintod Aug 29, 2025
4bf4aaf
ran tox -e ruff, even though tox -e flake8 was fine
filintod Aug 30, 2025
6ae63f4
Merge branch 'main' into filinto/tool-calling
elena-kolevska Sep 3, 2025
1e3baec
add tests to increase coverage in conversation and conversation_helpe…
filintod Sep 3, 2025
924541c
add more information on execute registered tools, also added more tes…
filintod Sep 3, 2025
9dd3bbf
fix test failing on py 1.13. Merge two unit test files per feedback
filintod Sep 4, 2025
5e1bab3
Linter
elena-kolevska Sep 4, 2025
bc3dcf0
fix typing issue with UnionType in py3.9
filintod Sep 4, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 180 additions & 2 deletions dapr/aio/clients/grpc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
to_bytes,
validateNotNone,
validateNotBlankString,
convert_parameters,
convert_value_to_struct,
)
from dapr.aio.clients.grpc._request import (
EncryptRequestIterator,
Expand All @@ -77,12 +79,21 @@
BindingRequest,
TransactionalStateOperation,
ConversationInput,
ConversationInputAlpha2,
ConversationMessage,
ConversationMessageContent,
ConversationTools,
ConversationToolCalls,
)
from dapr.clients.grpc._jobs import Job
from dapr.clients.grpc._response import (
BindingResponse,
ConversationResponse,
ConversationResult,
ConversationResponseAlpha2,
ConversationResultAlpha2,
ConversationResultChoices,
ConversationResultMessage,
DaprResponse,
GetSecretResponse,
GetBulkSecretResponse,
Expand Down Expand Up @@ -1733,7 +1744,7 @@ async def converse_alpha1(
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
parameters: Optional custom parameters for the request (raw Python values or GrpcAny objects)
metadata: Optional metadata for the component
scrub_pii: Optional flag to scrub PII from inputs and outputs
temperature: Optional temperature setting for the LLM to optimize for creativity or predictability
Expand All @@ -1749,11 +1760,14 @@ async def converse_alpha1(
for inp in inputs
]

# # Convert raw Python parameters to GrpcAny objects
# converted_parameters = convert_parameters(parameters)

request = api_v1.ConversationRequest(
name=name,
inputs=inputs_pb,
contextID=context_id,
parameters=parameters or {},
parameters=parameters,
metadata=metadata or {},
scrubPII=scrub_pii,
temperature=temperature,
Expand All @@ -1772,6 +1786,170 @@ async def converse_alpha1(
except grpc.aio.AioRpcError as err:
raise DaprGrpcError(err) from err

async def converse_alpha2(
self,
name: str,
inputs: List[ConversationInputAlpha2],
*,
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,
tools: Optional[List[ConversationTools]] = None,
tool_choice: Optional[str] = None,
) -> ConversationResponseAlpha2:
"""Invoke an LLM using the conversation API (Alpha2) with tool calling support.

Args:
name: Name of the LLM component to invoke
inputs: List of Alpha2 conversation inputs with sophisticated message types
context_id: Optional ID for continuing an existing chat
parameters: Optional custom parameters for the request (raw Python values or GrpcAny objects)
metadata: Optional metadata for the component
scrub_pii: Optional flag to scrub PII from inputs and outputs
temperature: Optional temperature setting for the LLM to optimize for creativity or predictability
tools: Optional list of tools available for the LLM to call
tool_choice: Optional control over which tools can be called ('none', 'auto', 'required', or specific tool name)

Returns:
ConversationResponseAlpha2 containing the conversation results with choices and tool calls

Raises:
DaprGrpcError: If the Dapr runtime returns an error
"""

def _convert_message_content(content_list: List[ConversationMessageContent]):
"""Convert message content list to proto format."""
if not content_list:
return []
return [
api_v1.ConversationMessageContent(text=content.text) for content in content_list
]

def _convert_tool_calls(tool_calls: List[ConversationToolCalls]):
"""Convert tool calls to proto format."""
if not tool_calls:
return []
proto_calls = []
for call in tool_calls:
proto_call = api_v1.ConversationToolCalls()
if call.id:
proto_call.id = call.id
if call.function:
proto_call.function.name = call.function.name
proto_call.function.arguments = call.function.arguments
proto_calls.append(proto_call)
return proto_calls

def _convert_message(message: ConversationMessage):
"""Convert a conversation message to proto format."""
proto_message = api_v1.ConversationMessage()

if message.of_developer:
proto_message.of_developer.name = message.of_developer.name or ''
proto_message.of_developer.content.extend(
_convert_message_content(message.of_developer.content or [])
)
elif message.of_system:
proto_message.of_system.name = message.of_system.name or ''
proto_message.of_system.content.extend(
_convert_message_content(message.of_system.content or [])
)
elif message.of_user:
proto_message.of_user.name = message.of_user.name or ''
proto_message.of_user.content.extend(
_convert_message_content(message.of_user.content or [])
)
elif message.of_assistant:
proto_message.of_assistant.name = message.of_assistant.name or ''
proto_message.of_assistant.content.extend(
_convert_message_content(message.of_assistant.content or [])
)
proto_message.of_assistant.tool_calls.extend(
_convert_tool_calls(message.of_assistant.tool_calls or [])
)
elif message.of_tool:
if message.of_tool.tool_id:
proto_message.of_tool.tool_id = message.of_tool.tool_id
proto_message.of_tool.name = message.of_tool.name
proto_message.of_tool.content.extend(
_convert_message_content(message.of_tool.content or [])
)

return proto_message

# Convert inputs to proto format
inputs_pb = []
for inp in inputs:
proto_input = api_v1.ConversationInputAlpha2()
if inp.scrub_pii is not None:
proto_input.scrub_pii = inp.scrub_pii

for message in inp.messages:
proto_input.messages.append(_convert_message(message))

inputs_pb.append(proto_input)

# Convert tools to proto format
tools_pb = []
if tools:
for tool in tools:
proto_tool = api_v1.ConversationTools()
if tool.function:
proto_tool.function.name = tool.function.name
if tool.function.description:
proto_tool.function.description = tool.function.description
if tool.function.parameters:
proto_tool.function.parameters.CopyFrom(
convert_value_to_struct(tool.function.parameters)
)
tools_pb.append(proto_tool)

# Convert raw Python parameters to GrpcAny objects
converted_parameters = convert_parameters(parameters)

# Build the request
request = api_v1.ConversationRequestAlpha2(
name=name,
inputs=inputs_pb,
parameters=converted_parameters,
metadata=metadata or {},
tools=tools_pb,
)

if context_id is not None:
request.context_id = context_id
if scrub_pii is not None:
request.scrub_pii = scrub_pii
if temperature is not None:
request.temperature = temperature
if tool_choice is not None:
request.tool_choice = tool_choice

try:
response = await self._stub.ConverseAlpha2(request)

outputs = []
for output in response.outputs:
choices = []
for choice in output.choices:
choices.append(
ConversationResultChoices(
finish_reason=choice.finish_reason,
index=choice.index,
message=ConversationResultMessage(
content=choice.message.content, tool_calls=choice.message.tool_calls
),
)
)
outputs.append(ConversationResultAlpha2(choices=choices))

return ConversationResponseAlpha2(context_id=response.context_id, outputs=outputs)

except grpc.aio.AioRpcError as err:
raise DaprGrpcError(err) from err

async def wait(self, timeout_s: float):
"""Waits for sidecar to be available within the timeout.

Expand Down
25 changes: 25 additions & 0 deletions dapr/clients/_constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-

"""
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.
"""

"""
Internal constants for the Dapr clients package.

This module contains shared constants that can be imported by various
client modules without creating circular dependencies.
"""

# Encoding and content type constants
DEFAULT_ENCODING = 'utf-8'
DEFAULT_JSON_CONTENT_TYPE = f'application/json; charset={DEFAULT_ENCODING}'
4 changes: 0 additions & 4 deletions dapr/clients/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,6 @@
from typing import Optional


DEFAULT_ENCODING = 'utf-8'
DEFAULT_JSON_CONTENT_TYPE = f'application/json; charset={DEFAULT_ENCODING}'


class DaprActorClientBase(ABC):
"""A base class that represents Dapr Actor Client."""

Expand Down
Loading