Skip to content

Commit 869616b

Browse files
refactor: remove ToolDefinition, merge custom parameters into config types
Co-Authored-By: traci@launchdarkly.com <traci@launchdarkly.com>
1 parent 1d3d52f commit 869616b

5 files changed

Lines changed: 131 additions & 154 deletions

File tree

packages/sdk/server-ai/src/ldai/__init__.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
LDMessage,
2929
ModelConfig,
3030
ProviderConfig,
31-
ToolDefinition,
3231
)
3332
from ldai.providers import (
3433
AgentGraphResult,
@@ -69,7 +68,6 @@
6968
'LDMessage',
7069
'ModelConfig',
7170
'ProviderConfig',
72-
'ToolDefinition',
7371
'log',
7472
# Deprecated exports
7573
'AIConfig',

packages/sdk/server-ai/src/ldai/client.py

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
LDMessage,
2626
ModelConfig,
2727
ProviderConfig,
28-
ToolDefinition,
2928
)
3029
from ldai.providers import ToolRegistry
3130
from ldai.providers.runner_factory import RunnerFactory
@@ -69,7 +68,9 @@ def _completion_config(
6968
default: AICompletionConfigDefault,
7069
variables: Optional[Dict[str, Any]] = None,
7170
) -> AICompletionConfig:
72-
model, provider, messages, instructions, tracker, enabled, judge_configuration, _, tools = self.__evaluate(
71+
(model, provider, messages, instructions,
72+
tracker, enabled, judge_configuration,
73+
_, tool_custom_parameters) = self.__evaluate(
7374
key, context, default.to_dict(), variables
7475
)
7576

@@ -81,7 +82,7 @@ def _completion_config(
8182
provider=provider,
8283
tracker=tracker,
8384
judge_configuration=judge_configuration,
84-
tools=tools,
85+
tool_custom_parameters=tool_custom_parameters,
8586
)
8687

8788
return config
@@ -138,7 +139,7 @@ def _judge_config(
138139
) -> AIJudgeConfig:
139140
(model, provider, messages, instructions,
140141
tracker, enabled, judge_configuration,
141-
variation, tools) = self.__evaluate(
142+
variation, _) = self.__evaluate(
142143
key, context, default.to_dict(), variables
143144
)
144145

@@ -755,7 +756,7 @@ def __evaluate(
755756
) -> Tuple[
756757
Optional[ModelConfig], Optional[ProviderConfig], Optional[List[LDMessage]],
757758
Optional[str], LDAIConfigTracker, bool, Optional[Any], Dict[str, Any],
758-
Optional[List[ToolDefinition]]
759+
Optional[Dict[str, Dict[str, Any]]]
759760
]:
760761
"""
761762
Internal method to evaluate a configuration and extract components.
@@ -833,23 +834,21 @@ def __evaluate(
833834
if judges:
834835
judge_configuration = JudgeConfiguration(judges=judges)
835836

836-
tools = None
837+
tool_custom_parameters = None
837838
model_raw = variation.get('model')
838839
params_raw = model_raw.get('parameters') if isinstance(model_raw, dict) else None
839840
tool_defs_raw = params_raw.get('tools') if isinstance(params_raw, dict) else None
840841
if isinstance(tool_defs_raw, list):
841-
tools = [
842-
ToolDefinition(
843-
name=t.get('name', ''),
844-
custom_parameters=t.get('customParameters', None)
845-
)
846-
for t in tool_defs_raw
847-
if isinstance(t, dict) and t.get('name')
848-
]
849-
if not tools:
850-
tools = None
842+
parsed: Dict[str, Dict[str, Any]] = {}
843+
for t in tool_defs_raw:
844+
if isinstance(t, dict) and t.get('name'):
845+
parsed[t['name']] = t.get('customParameters') or {}
846+
if parsed:
847+
tool_custom_parameters = parsed
851848

852-
return model, provider_config, messages, instructions, tracker, enabled, judge_configuration, variation, tools
849+
return (model, provider_config, messages, instructions,
850+
tracker, enabled, judge_configuration,
851+
variation, tool_custom_parameters)
853852

854853
def __evaluate_agent(
855854
self,
@@ -867,7 +866,9 @@ def __evaluate_agent(
867866
:param variables: Variables for interpolation.
868867
:return: Configured AIAgentConfig instance.
869868
"""
870-
model, provider, messages, instructions, tracker, enabled, judge_configuration, _, tools = self.__evaluate(
869+
(model, provider, messages, instructions,
870+
tracker, enabled, judge_configuration,
871+
_, tool_custom_parameters) = self.__evaluate(
871872
key, context, default.to_dict(), variables
872873
)
873874

@@ -882,7 +883,7 @@ def __evaluate_agent(
882883
instructions=final_instructions,
883884
tracker=tracker,
884885
judge_configuration=judge_configuration or default.judge_configuration,
885-
tools=tools or default.tools,
886+
tool_custom_parameters=tool_custom_parameters or default.tool_custom_parameters,
886887
)
887888

888889
def __interpolate_template(self, template: str, variables: Dict[str, Any]) -> str:

packages/sdk/server-ai/src/ldai/models.py

Lines changed: 80 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -77,57 +77,6 @@ def to_dict(self) -> dict:
7777
}
7878

7979

80-
class ToolDefinition:
81-
"""
82-
Definition of a tool available to an AI configuration.
83-
84-
Each tool has a name used to match against the tool registry,
85-
and optional custom parameters that can be configured via the
86-
LaunchDarkly dashboard.
87-
"""
88-
89-
def __init__(
90-
self,
91-
name: str,
92-
custom_parameters: Optional[Dict[str, Any]] = None,
93-
):
94-
"""
95-
:param name: The name of the tool.
96-
:param custom_parameters: Optional custom parameters for
97-
the tool, configured via the LaunchDarkly dashboard.
98-
"""
99-
self._name = name
100-
self._custom_parameters = custom_parameters
101-
102-
@property
103-
def name(self) -> str:
104-
"""
105-
The name of the tool.
106-
"""
107-
return self._name
108-
109-
def get_custom_parameter(self, key: str) -> Any:
110-
"""
111-
Retrieve a custom parameter by key.
112-
113-
:param key: The custom parameter key to look up.
114-
:return: The parameter value, or None if not found.
115-
"""
116-
if self._custom_parameters is None:
117-
return None
118-
119-
return self._custom_parameters.get(key)
120-
121-
def to_dict(self) -> dict:
122-
"""
123-
Render the tool definition as a dictionary object.
124-
"""
125-
result: Dict[str, Any] = {'name': self._name}
126-
if self._custom_parameters is not None:
127-
result['customParameters'] = self._custom_parameters
128-
return result
129-
130-
13180
class ProviderConfig:
13281
"""
13382
Configuration related to the provider.
@@ -259,7 +208,22 @@ class AICompletionConfigDefault(AIConfigDefault):
259208
"""
260209
messages: Optional[List[LDMessage]] = None
261210
judge_configuration: Optional[JudgeConfiguration] = None
262-
tools: Optional[List[ToolDefinition]] = None
211+
tool_custom_parameters: Optional[Dict[str, Dict[str, Any]]] = None
212+
213+
def get_tool_custom_parameter(self, tool_name: str, key: str) -> Any:
214+
"""
215+
Retrieve a custom parameter for a specific tool.
216+
217+
:param tool_name: The name of the tool.
218+
:param key: The custom parameter key to look up.
219+
:return: The parameter value, or None if not found.
220+
"""
221+
if self.tool_custom_parameters is None:
222+
return None
223+
tool_params = self.tool_custom_parameters.get(tool_name)
224+
if tool_params is None:
225+
return None
226+
return tool_params.get(key)
263227

264228
def to_dict(self) -> dict:
265229
"""
@@ -269,10 +233,16 @@ def to_dict(self) -> dict:
269233
result['messages'] = [message.to_dict() for message in self.messages] if self.messages else None
270234
if self.judge_configuration is not None:
271235
result['judgeConfiguration'] = self.judge_configuration.to_dict()
272-
if self.tools is not None:
236+
if self.tool_custom_parameters is not None:
273237
model = result.get('model') or {}
274238
params = model.get('parameters') or {}
275-
params['tools'] = [tool.to_dict() for tool in self.tools]
239+
tools_list = []
240+
for name, custom_params in self.tool_custom_parameters.items():
241+
tool_entry: Dict[str, Any] = {'name': name}
242+
if custom_params:
243+
tool_entry['customParameters'] = custom_params
244+
tools_list.append(tool_entry)
245+
params['tools'] = tools_list
276246
model['parameters'] = params
277247
result['model'] = model
278248
return result
@@ -285,7 +255,22 @@ class AICompletionConfig(AIConfig):
285255
"""
286256
messages: Optional[List[LDMessage]] = None
287257
judge_configuration: Optional[JudgeConfiguration] = None
288-
tools: Optional[List[ToolDefinition]] = None
258+
tool_custom_parameters: Optional[Dict[str, Dict[str, Any]]] = None
259+
260+
def get_tool_custom_parameter(self, tool_name: str, key: str) -> Any:
261+
"""
262+
Retrieve a custom parameter for a specific tool.
263+
264+
:param tool_name: The name of the tool.
265+
:param key: The custom parameter key to look up.
266+
:return: The parameter value, or None if not found.
267+
"""
268+
if self.tool_custom_parameters is None:
269+
return None
270+
tool_params = self.tool_custom_parameters.get(tool_name)
271+
if tool_params is None:
272+
return None
273+
return tool_params.get(key)
289274

290275
def to_dict(self) -> dict:
291276
"""
@@ -309,7 +294,22 @@ class AIAgentConfigDefault(AIConfigDefault):
309294
"""
310295
instructions: Optional[str] = None
311296
judge_configuration: Optional[JudgeConfiguration] = None
312-
tools: Optional[List[ToolDefinition]] = None
297+
tool_custom_parameters: Optional[Dict[str, Dict[str, Any]]] = None
298+
299+
def get_tool_custom_parameter(self, tool_name: str, key: str) -> Any:
300+
"""
301+
Retrieve a custom parameter for a specific tool.
302+
303+
:param tool_name: The name of the tool.
304+
:param key: The custom parameter key to look up.
305+
:return: The parameter value, or None if not found.
306+
"""
307+
if self.tool_custom_parameters is None:
308+
return None
309+
tool_params = self.tool_custom_parameters.get(tool_name)
310+
if tool_params is None:
311+
return None
312+
return tool_params.get(key)
313313

314314
def to_dict(self) -> Dict[str, Any]:
315315
"""
@@ -320,10 +320,16 @@ def to_dict(self) -> Dict[str, Any]:
320320
result['instructions'] = self.instructions
321321
if self.judge_configuration is not None:
322322
result['judgeConfiguration'] = self.judge_configuration.to_dict()
323-
if self.tools is not None:
323+
if self.tool_custom_parameters is not None:
324324
model = result.get('model') or {}
325325
params = model.get('parameters') or {}
326-
params['tools'] = [tool.to_dict() for tool in self.tools]
326+
tools_list = []
327+
for name, custom_params in self.tool_custom_parameters.items():
328+
tool_entry: Dict[str, Any] = {'name': name}
329+
if custom_params:
330+
tool_entry['customParameters'] = custom_params
331+
tools_list.append(tool_entry)
332+
params['tools'] = tools_list
327333
model['parameters'] = params
328334
result['model'] = model
329335
return result
@@ -336,7 +342,22 @@ class AIAgentConfig(AIConfig):
336342
"""
337343
instructions: Optional[str] = None
338344
judge_configuration: Optional[JudgeConfiguration] = None
339-
tools: Optional[List[ToolDefinition]] = None
345+
tool_custom_parameters: Optional[Dict[str, Dict[str, Any]]] = None
346+
347+
def get_tool_custom_parameter(self, tool_name: str, key: str) -> Any:
348+
"""
349+
Retrieve a custom parameter for a specific tool.
350+
351+
:param tool_name: The name of the tool.
352+
:param key: The custom parameter key to look up.
353+
:return: The parameter value, or None if not found.
354+
"""
355+
if self.tool_custom_parameters is None:
356+
return None
357+
tool_params = self.tool_custom_parameters.get(tool_name)
358+
if tool_params is None:
359+
return None
360+
return tool_params.get(key)
340361

341362
def to_dict(self) -> Dict[str, Any]:
342363
"""

packages/sdk/server-ai/tests/test_agents.py

Lines changed: 14 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from ldclient.integrations.test_data import TestData
44

55
from ldai import (LDAIAgentConfig, LDAIAgentDefaults, LDAIClient, ModelConfig,
6-
ProviderConfig, ToolDefinition)
6+
ProviderConfig)
77

88

99
@pytest.fixture
@@ -396,49 +396,40 @@ def test_agent_config_has_tools(ldai_client: LDAIClient):
396396
agent = ldai_client.agent_config('agent-with-tools', context)
397397

398398
assert agent.enabled is True
399-
assert agent.tools is not None
400-
assert len(agent.tools) == 3
399+
assert agent.tool_custom_parameters is not None
400+
assert len(agent.tool_custom_parameters) == 3
401401

402-
get_order = agent.tools[0]
403-
assert get_order.name == 'get-order'
404-
assert get_order.get_custom_parameter('includeHistory') is True
405-
assert get_order.get_custom_parameter('maxItems') == 5
406-
407-
search = agent.tools[1]
408-
assert search.name == 'search-products'
409-
assert search.get_custom_parameter('category') == 'electronics'
410-
411-
send_email = agent.tools[2]
412-
assert send_email.name == 'send-email'
413-
assert send_email.get_custom_parameter('anything') is None
402+
assert agent.get_tool_custom_parameter('get-order', 'includeHistory') is True
403+
assert agent.get_tool_custom_parameter('get-order', 'maxItems') == 5
404+
assert agent.get_tool_custom_parameter('search-products', 'category') == 'electronics'
405+
assert agent.get_tool_custom_parameter('send-email', 'anything') is None
406+
assert 'send-email' in agent.tool_custom_parameters
414407

415408

416409
def test_agent_config_tools_fallback_to_default(ldai_client: LDAIClient):
417410
"""Test that agent config falls back to default tools when flag has no tools."""
418411
context = Context.create('user-key')
419-
default_tools = [ToolDefinition('default-tool', custom_parameters={'timeout': 30})]
420412
default = LDAIAgentDefaults(
421413
enabled=False,
422414
model=ModelConfig('fallback-model'),
423415
instructions='Default instructions',
424-
tools=default_tools,
416+
tool_custom_parameters={'default-tool': {'timeout': 30}},
425417
)
426418

427419
agent = ldai_client.agent_config('customer-support-agent', context, default)
428420

429421
assert agent.enabled is True
430422
# customer-support-agent has no tools in the flag, so falls back to default
431-
assert agent.tools is not None
432-
assert len(agent.tools) == 1
433-
assert agent.tools[0].name == 'default-tool'
434-
assert agent.tools[0].get_custom_parameter('timeout') == 30
423+
assert agent.tool_custom_parameters is not None
424+
assert len(agent.tool_custom_parameters) == 1
425+
assert agent.get_tool_custom_parameter('default-tool', 'timeout') == 30
435426

436427

437428
def test_agent_config_no_tools(ldai_client: LDAIClient):
438-
"""Test that agent tools is None when neither flag nor default has tools."""
429+
"""Test that tool_custom_parameters is None when neither flag nor default has tools."""
439430
context = Context.create('user-key')
440431

441432
agent = ldai_client.agent_config('customer-support-agent', context)
442433

443434
assert agent.enabled is True
444-
assert agent.tools is None
435+
assert agent.tool_custom_parameters is None

0 commit comments

Comments
 (0)