Skip to content

Commit cd255bd

Browse files
filintodsicoyle
andauthored
make dapr the default and add component_name option to dapr client (#211)
* make dapr the default and add component_name option to dapr client Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * lint Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * fixes for tests, and embedder missing method Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * lint Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * update requirments.txt file Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * add checks against dapr runtime Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * lint Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * fix tests Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * lint Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * Update dapr_agents/llm/dapr/chat.py Co-authored-by: Sam <sam@diagrid.io> Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * Update quickstarts/03-durable-agent-multitool-dapr/multi_tool_agent_dapr.py Co-authored-by: Sam <sam@diagrid.io> Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * fix: few minor tweaks before release (#215) Signed-off-by: Sam <sam@diagrid.io> * fix: rm finally block (#216) * fix: rm finally block Signed-off-by: Sam <sam@diagrid.io> * fix: no need to shutdown Signed-off-by: Sam <sam@diagrid.io> --------- Signed-off-by: Sam <sam@diagrid.io> * move quickstarts doc to dev doc location Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> * lint Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> --------- Signed-off-by: Filinto Duran <1373693+filintod@users.noreply.github.com> Signed-off-by: Sam <sam@diagrid.io> Co-authored-by: Sam <sam@diagrid.io>
1 parent a346cee commit cd255bd

File tree

23 files changed

+577
-66
lines changed

23 files changed

+577
-66
lines changed

dapr_agents/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dapr_agents.agents.agent import Agent
22
from dapr_agents.agents.durableagent import DurableAgent
33
from dapr_agents.executors import DockerCodeExecutor, LocalCodeExecutor
4+
from dapr_agents.llm.dapr import DaprChatClient
45
from dapr_agents.llm.elevenlabs import ElevenLabsSpeechClient
56
from dapr_agents.llm.huggingface import HFHubChatClient
67
from dapr_agents.llm.nvidia import NVIDIAChatClient, NVIDIAEmbeddingClient
@@ -24,6 +25,7 @@
2425
"DockerCodeExecutor",
2526
"LocalCodeExecutor",
2627
"ElevenLabsSpeechClient",
28+
"DaprChatClient",
2729
"HFHubChatClient",
2830
"NVIDIAChatClient",
2931
"NVIDIAEmbeddingClient",

dapr_agents/agents/base.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
)
2828
from pydantic import BaseModel, Field, PrivateAttr, model_validator, ConfigDict
2929
from dapr_agents.llm.chat import ChatClientBase
30+
from dapr_agents.llm.utils.defaults import get_default_llm
3031

3132
logger = logging.getLogger(__name__)
3233

@@ -166,7 +167,7 @@ def model_post_init(self, __context: Any) -> None:
166167

167168
# Initialize LLM if not provided
168169
if self.llm is None:
169-
self.llm = self._create_default_llm()
170+
self.llm = get_default_llm()
170171

171172
# Centralize prompt template selection logic
172173
self.prompt_template = self._initialize_prompt_template()
@@ -183,21 +184,6 @@ def model_post_init(self, __context: Any) -> None:
183184

184185
super().model_post_init(__context)
185186

186-
def _create_default_llm(self) -> Optional[ChatClientBase]:
187-
"""
188-
Creates a default LLM client when none is provided.
189-
Returns None if the default LLM cannot be created due to missing configuration.
190-
"""
191-
try:
192-
from dapr_agents.llm.openai import OpenAIChatClient
193-
194-
return OpenAIChatClient()
195-
except Exception as e:
196-
logger.warning(
197-
f"Failed to create default OpenAI client: {e}. LLM will be None."
198-
)
199-
return None
200-
201187
def _initialize_prompt_template(self) -> PromptTemplateBase:
202188
"""
203189
Determines which prompt template to use for the agent:

dapr_agents/agents/durableagent/agent.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import logging
44
from datetime import datetime, timezone
5-
from typing import Any, Callable, Dict, List, Optional, Union
5+
from typing import Any, Dict, List, Optional, Union
66

77
from dapr.ext.workflow import DaprWorkflowContext # type: ignore
88
from pydantic import Field, model_validator

dapr_agents/llm/dapr/chat.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@
2222
from dapr_agents.prompt.base import PromptTemplateBase
2323
from dapr_agents.prompt.prompty import Prompty
2424
from dapr_agents.tool import AgentTool
25+
from dapr_agents.types.exceptions import DaprRuntimeVersionNotSupportedError
2526
from dapr_agents.types.message import (
2627
BaseMessage,
2728
LLMChatResponse,
2829
)
30+
from dapr_agents.utils import is_version_supported
2931

3032

3133
# Lazy import to avoid import issues during test collection
@@ -76,6 +78,8 @@ class DaprChatClient(DaprInferenceClientBase, ChatClientBase):
7678
default=None, description="Optional prompt-template to format inputs."
7779
)
7880

81+
component_name: Optional[str] = None
82+
7983
# Only function_call–style structured output is supported
8084
SUPPORTED_STRUCTURED_MODES: ClassVar[set[str]] = {"function_call"}
8185

@@ -84,7 +88,13 @@ def model_post_init(self, __context: Any) -> None:
8488
After Pydantic init, set up API/type and default LLM component from env.
8589
"""
8690
self._api = "chat"
87-
self._llm_component = os.environ["DAPR_LLM_COMPONENT_DEFAULT"]
91+
self._llm_component = self.component_name
92+
if not self._llm_component:
93+
self._llm_component = os.environ.get("DAPR_LLM_COMPONENT_DEFAULT")
94+
if not self._llm_component:
95+
raise ValueError(
96+
"You must provide a component_name or set DAPR_LLM_COMPONENT_DEFAULT in the environment."
97+
)
8898
super().model_post_init(__context)
8999

90100
@classmethod
@@ -315,6 +325,19 @@ def generate(
315325
logger.debug(
316326
f"Alpha2 parameters keys: {list(params.get('parameters', {}).keys())}"
317327
)
328+
# get metadata information from the dapr client
329+
metadata = self.client.dapr_client.get_metadata()
330+
extended_metadata = metadata.extended_metadata
331+
dapr_runtime_version = extended_metadata.get("daprRuntimeVersion", None)
332+
if dapr_runtime_version is not None:
333+
# Allow only versions >=1.16.0 and <2.0.0 for Alpha2 Chat Client
334+
if not is_version_supported(
335+
str(dapr_runtime_version), ">=1.16.0, <2.0.0"
336+
):
337+
raise DaprRuntimeVersionNotSupportedError(
338+
f"!!!!! Dapr Runtime Version {dapr_runtime_version} is not supported with Alpha2 Dapr Chat Client. Only Dapr runtime versions >=1.16.0 and <2.0.0 are supported."
339+
)
340+
318341
raw = self.client.chat_completion_alpha2(
319342
llm=llm_component or self._llm_component,
320343
inputs=conv_inputs,

dapr_agents/llm/utils/defaults.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import logging
2+
from typing import Optional
3+
4+
from dapr_agents.llm.chat import ChatClientBase
5+
6+
logger = logging.getLogger(__name__)
7+
8+
9+
def get_default_llm() -> Optional[ChatClientBase]:
10+
"""
11+
Centralized default LLM factory for the SDK.
12+
13+
Returns:
14+
Optional[ChatClientBase]: A configured default LLM client or None if not available.
15+
"""
16+
try:
17+
from dapr_agents.llm.dapr import DaprChatClient
18+
19+
return DaprChatClient()
20+
except Exception as e:
21+
logger.warning(f"Failed to create default Dapr client: {e}. LLM will be None.")
22+
raise

dapr_agents/types/exceptions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,11 @@ class StructureError(Exception):
1616

1717
class FunCallBuilderError(Exception):
1818
"""Custom exception for errors related to structured handling."""
19+
20+
21+
class NotSupportedError(Exception):
22+
"""Custom exception for errors related to not supported features or versions."""
23+
24+
25+
class DaprRuntimeVersionNotSupportedError(NotSupportedError):
26+
"""Custom exception for errors related to not supported Dapr runtime versions."""

dapr_agents/utils/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from .semver import is_version_supported # re-export for convenience
2+
13
from .signal_handlers import add_signal_handlers_cross_platform
24
from .signal_mixin import SignalHandlingMixin
35

dapr_agents/utils/semver.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from __future__ import annotations
2+
3+
from dataclasses import dataclass
4+
from typing import List, Tuple
5+
6+
7+
@dataclass(frozen=True, order=True)
8+
class Version:
9+
major: int
10+
minor: int
11+
patch: int
12+
13+
@staticmethod
14+
def parse(version: str) -> "Version":
15+
"""Parse a semver-like string 'MAJOR.MINOR.PATCH' ignoring pre-release/build.
16+
17+
Non-numeric or missing parts default to 0; extra parts are ignored.
18+
Examples: '1.16.0', '1.16', '1' -> (1,16,0)/(1,0,0).
19+
"""
20+
core = version.split("-")[0].split("+")[0]
21+
parts = core.split(".")
22+
nums: List[int] = []
23+
for i in range(3):
24+
try:
25+
nums.append(int(parts[i]))
26+
except Exception:
27+
nums.append(0)
28+
return Version(nums[0], nums[1], nums[2])
29+
30+
31+
def _parse_constraint_token(token: str) -> Tuple[str, Version]:
32+
token = token.strip()
33+
ops = ["<=", ">=", "==", "!=", "<", ">"]
34+
for op in ops:
35+
if token.startswith(op):
36+
return op, Version.parse(token[len(op) :].strip())
37+
# default to == if no operator present
38+
return "==", Version.parse(token)
39+
40+
41+
def _satisfies(version: Version, op: str, bound: Version) -> bool:
42+
if op == "==":
43+
return version == bound
44+
if op == "!=":
45+
return version != bound
46+
if op == ">":
47+
return version > bound
48+
if op == ">=":
49+
return version >= bound
50+
if op == "<":
51+
return version < bound
52+
if op == "<=":
53+
return version <= bound
54+
raise ValueError(f"Unknown operator: {op}")
55+
56+
57+
def is_version_supported(version: str, constraints: str) -> bool:
58+
"""Return True if the given version satisfies the constraints.
59+
60+
Constraints syntax:
61+
- Comma-separated items are ANDed: ">=1.16.0, <2.0.0"
62+
- Use '||' for OR groups: ">=1.16.0, <2.0.0 || ==0.0.0"
63+
- Each token supports operators: ==, !=, >=, <=, >, <
64+
- Missing operator defaults to ==
65+
"""
66+
v = Version.parse(version)
67+
for group in constraints.split("||"):
68+
group = group.strip()
69+
if not group:
70+
continue
71+
tokens = [t for t in (tok.strip() for tok in group.split(",")) if t]
72+
if not tokens:
73+
continue
74+
if all(_satisfies(v, *_parse_constraint_token(tok)) for tok in tokens):
75+
return True
76+
return False

dapr_agents/workflow/base.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
from datetime import datetime, timezone
99
from typing import Any, Callable, Dict, List, Optional, TypeVar, Union
1010

11-
from pydantic import BaseModel
1211
from dapr.ext.workflow import (
1312
DaprWorkflowClient,
1413
WorkflowActivityContext,
1514
WorkflowRuntime,
1615
)
1716
from dapr.ext.workflow.workflow_state import WorkflowState
1817
from durabletask import task as dtask
19-
from pydantic import ConfigDict, Field
18+
from pydantic import BaseModel, ConfigDict, Field
2019

2120
from dapr_agents.agents.base import ChatClientBase
21+
from dapr_agents.llm.utils.defaults import get_default_llm
2222
from dapr_agents.types.workflow import DaprWorkflowStatus
2323
from dapr_agents.utils import SignalHandlingMixin
2424
from dapr_agents.workflow.task import WorkflowTask
@@ -73,7 +73,7 @@ def model_post_init(self, __context: Any) -> None:
7373
"""
7474
# Initialize LLM first
7575
if self.llm is None:
76-
self.llm = self._create_default_llm()
76+
self.llm = get_default_llm()
7777

7878
# Initialize clients and runtime
7979
self.wf_runtime = WorkflowRuntime()
@@ -91,21 +91,6 @@ def model_post_init(self, __context: Any) -> None:
9191

9292
super().model_post_init(__context)
9393

94-
def _create_default_llm(self) -> Optional[ChatClientBase]:
95-
"""
96-
Creates a default LLM client when none is provided.
97-
Returns None if the default LLM cannot be created due to missing configuration.
98-
"""
99-
try:
100-
from dapr_agents.llm.openai import OpenAIChatClient
101-
102-
return OpenAIChatClient()
103-
except Exception as e:
104-
logger.warning(
105-
f"Failed to create default OpenAI client: {e}. LLM will be None."
106-
)
107-
return None
108-
10994
def graceful_shutdown(self) -> None:
11095
"""
11196
Perform graceful shutdown operations for the WorkflowApp.

0 commit comments

Comments
 (0)