Skip to content

Commit 4e28ae6

Browse files
committed
refactor: address review feedback on docstrings
1 parent 5c4181c commit 4e28ae6

5 files changed

Lines changed: 94 additions & 76 deletions

File tree

packages/sdk/server-ai/src/ldai/providers/runner.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,7 @@ class Runner(Protocol):
1111
Unified runtime capability interface for all AI provider runners.
1212
1313
A :class:`Runner` is a focused, configured object that performs a single
14-
AI invocation. Both model runners and agent runners implement this protocol.
15-
16-
:param input: The input to the runner (string prompt, list of messages, or
17-
other provider-specific input type).
18-
:param output_type: Optional JSON schema dict that requests structured output.
19-
When provided, the runner populates :attr:`~RunnerResult.parsed` on the
20-
returned :class:`RunnerResult`.
21-
:return: :class:`RunnerResult` containing ``content``, ``metrics``, and
22-
optionally ``raw`` and ``parsed``.
14+
AI invocation.
2315
"""
2416

2517
async def run(

packages/sdk/server-ai/src/ldai/providers/types.py

Lines changed: 51 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,22 +16,19 @@
1616

1717
@dataclass
1818
class LDAIMetrics:
19-
"""
20-
Metrics information for AI operations that includes success status, token
21-
usage, and optional enrichment fields populated by runners.
22-
23-
``tool_calls`` is a list of tool-call names observed during the invocation
24-
(populated by agent runners that execute tool loops).
19+
"""Contains metrics for a single AI invocation."""
2520

26-
``duration_ms`` is the wall-clock duration of the runner invocation in
27-
milliseconds, when measured by the runner itself rather than externally.
28-
When set, the tracker uses this value directly instead of measuring elapsed
29-
time.
30-
"""
3121
success: bool
22+
"""Whether the invocation succeeded."""
23+
3224
usage: Optional[TokenUsage] = None
25+
"""Optional token usage information."""
26+
3327
tool_calls: Optional[List[str]] = None
28+
"""Ordered list of tool-call names observed during the invocation."""
29+
3430
duration_ms: Optional[int] = None
31+
"""Wall-clock duration of the runner invocation in milliseconds."""
3532

3633
def to_dict(self) -> Dict[str, Any]:
3734
"""
@@ -55,36 +52,39 @@ def to_dict(self) -> Dict[str, Any]:
5552

5653
@dataclass
5754
class RunnerResult:
58-
"""
59-
Result returned by a :class:`~ldai.providers.runner.Runner` from a single
60-
invocation.
55+
"""Contains the result of a single AI model invocation."""
6156

62-
This is the unified return type for all Runner implementations.
63-
``evaluations`` is intentionally absent — judge evaluations are dispatched
64-
by the managed layer and live on :class:`ManagedResult`.
65-
"""
6657
content: str
58+
"""The text content returned by the model."""
59+
6760
metrics: LDAIMetrics
61+
"""Metrics for this invocation."""
62+
6863
raw: Optional[Any] = None
64+
"""Optional provider-native response object for advanced consumers."""
65+
6966
parsed: Optional[Dict[str, Any]] = None
67+
"""Optional parsed structured output, populated when ``output_type`` was supplied."""
7068

7169

7270
@dataclass
7371
class ManagedResult:
74-
"""
75-
Result returned by the managed layer (:class:`~ldai.ManagedModel` /
76-
:class:`~ldai.ManagedAgent`) after a single invocation.
72+
"""Contains the result of a managed AI invocation, including metrics and optional judge evaluations."""
7773

78-
``metrics`` is an :class:`~ldai.tracker.LDAIMetricSummary` (from
79-
``tracker.get_summary()``) rather than a raw :class:`LDAIMetrics`.
80-
``evaluations`` is an optional asyncio Task that resolves to a list of
81-
:class:`JudgeResult` instances when awaited.
82-
"""
8374
content: str
75+
"""The text content returned by the model."""
76+
8477
metrics: LDAIMetricSummary
78+
"""Aggregated metric summary from the tracker for this invocation."""
79+
8580
raw: Optional[Any] = None
81+
"""Optional provider-native response object for advanced consumers."""
82+
8683
parsed: Optional[Dict[str, Any]] = None
84+
"""Optional parsed structured output, populated when ``output_type`` was supplied."""
85+
8786
evaluations: Optional[asyncio.Task[List[JudgeResult]]] = None
87+
"""Optional asyncio Task that resolves to the list of :class:`JudgeResult` instances when awaited."""
8888

8989

9090
@dataclass
@@ -116,16 +116,28 @@ class StructuredResponse:
116116

117117
@dataclass
118118
class JudgeResult:
119-
"""
120-
Result from a judge evaluation.
121-
"""
119+
"""Contains the result of a single judge evaluation."""
120+
122121
judge_config_key: Optional[str] = None
122+
"""The configuration key of the judge that produced this result."""
123+
123124
success: bool = False
125+
"""Whether the judge evaluation completed successfully."""
126+
124127
error_message: Optional[str] = None
125-
sampled: bool = False # True when the evaluation was sampled and run
128+
"""Error message describing why the evaluation failed, if any."""
129+
130+
sampled: bool = False
131+
"""True when the evaluation was sampled and run."""
132+
126133
metric_key: Optional[str] = None
134+
"""The metric key under which this judge's score is reported."""
135+
127136
score: Optional[float] = None
137+
"""The numeric score (0-1) returned by the judge."""
138+
128139
reasoning: Optional[str] = None
140+
"""The judge's reasoning text accompanying the score."""
129141

130142
def to_dict(self) -> Dict[str, Any]:
131143
"""
@@ -164,10 +176,16 @@ class AgentResult:
164176

165177
@dataclass
166178
class AgentGraphResult:
167-
"""
168-
Result from an agent graph run.
169-
"""
179+
"""Contains the result of an agent graph run."""
180+
170181
output: str
182+
"""The agent graph's final output content."""
183+
171184
raw: Any
185+
"""The provider-native response object from the graph run."""
186+
172187
metrics: LDAIMetrics
188+
"""Metrics recorded during the graph run."""
189+
173190
evaluations: Optional[List[JudgeResult]] = None
191+
"""Optional list of judge evaluation results produced for the graph run."""

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

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
from ldai import LDAIClient, ManagedAgent
77
from ldai.managed_agent import ManagedAgent
88
from ldai.models import AIAgentConfig, AIAgentConfigDefault, ModelConfig, ProviderConfig
9-
from ldai.providers import AgentResult
10-
from ldai.providers.types import LDAIMetrics, ManagedResult
9+
from ldai.providers.types import LDAIMetrics, ManagedResult, RunnerResult
1110
from ldai.tracker import LDAIMetricSummary
1211

1312
from ldclient import Config, Context, LDClient
@@ -64,20 +63,20 @@ async def test_run_delegates_to_agent_runner(self):
6463
mock_config = MagicMock(spec=AIAgentConfig)
6564
mock_tracker = MagicMock()
6665
mock_tracker.track_metrics_of_async = AsyncMock(
67-
return_value=AgentResult(
68-
output="Test response",
69-
raw=None,
66+
return_value=RunnerResult(
67+
content="Test response",
7068
metrics=LDAIMetrics(success=True, usage=None),
69+
raw=None,
7170
)
7271
)
7372
mock_tracker.get_summary = MagicMock(return_value=_make_summary(True))
7473
mock_config.create_tracker = MagicMock(return_value=mock_tracker)
7574
mock_runner = MagicMock()
7675
mock_runner.run = AsyncMock(
77-
return_value=AgentResult(
78-
output="Test response",
79-
raw=None,
76+
return_value=RunnerResult(
77+
content="Test response",
8078
metrics=LDAIMetrics(success=True, usage=None),
79+
raw=None,
8180
)
8281
)
8382

@@ -96,10 +95,10 @@ async def test_run_uses_create_tracker_for_fresh_tracker(self):
9695
mock_config = MagicMock(spec=AIAgentConfig)
9796
fresh_tracker = MagicMock()
9897
fresh_tracker.track_metrics_of_async = AsyncMock(
99-
return_value=AgentResult(
100-
output="Fresh tracker response",
101-
raw=None,
98+
return_value=RunnerResult(
99+
content="Fresh tracker response",
102100
metrics=LDAIMetrics(success=True, usage=None),
101+
raw=None,
103102
)
104103
)
105104
fresh_tracker.get_summary = MagicMock(return_value=_make_summary(True))
@@ -163,7 +162,7 @@ async def test_returns_managed_agent_when_runner_available(self, ldai_client: LD
163162

164163
mock_runner = MagicMock()
165164
mock_runner.run = AsyncMock(
166-
return_value=AgentResult(output="Hello!", raw=None, metrics=LDAIMetrics(success=True, usage=None))
165+
return_value=RunnerResult(content="Hello!", metrics=LDAIMetrics(success=True, usage=None), raw=None)
167166
)
168167

169168
original = rf.RunnerFactory.create_agent

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

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@
99
from ldai.evaluator import Evaluator
1010
from ldai.managed_model import ManagedModel
1111
from ldai.models import AICompletionConfig, LDMessage, ModelConfig, ProviderConfig
12-
from ldai.providers.types import JudgeResult, LDAIMetrics, ManagedResult, ModelResponse
12+
from ldai.providers.types import JudgeResult, LDAIMetrics, ManagedResult, ModelResponse, RunnerResult
1313
from ldai.tracker import LDAIConfigTracker, LDAIMetricSummary
1414

1515

1616

17+
def _make_runner_result(content: str = 'response text') -> RunnerResult:
18+
return RunnerResult(
19+
content=content,
20+
metrics=LDAIMetrics(success=True, usage=None),
21+
)
22+
23+
1724
def _make_model_response(content: str = 'response text') -> ModelResponse:
1825
return ModelResponse(
1926
message=LDMessage(role='assistant', content=content),
@@ -30,7 +37,7 @@ def _make_summary() -> LDAIMetricSummary:
3037
def _make_config_with_tracker(evaluator: Evaluator) -> tuple[AICompletionConfig, MagicMock]:
3138
"""Build an AICompletionConfig with a fully-mocked tracker."""
3239
mock_tracker = MagicMock(spec=LDAIConfigTracker)
33-
mock_tracker.track_metrics_of_async = AsyncMock(return_value=_make_model_response())
40+
mock_tracker.track_metrics_of_async = AsyncMock(return_value=_make_runner_result())
3441
mock_tracker.get_summary = MagicMock(return_value=_make_summary())
3542
config = AICompletionConfig(
3643
key='test-config',
@@ -56,10 +63,10 @@ async def test_run_returns_managed_result(self):
5663
)
5764

5865
mock_runner = MagicMock()
59-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response('hi'))
66+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result('hi'))
6067

6168
mock_tracker = MagicMock(spec=LDAIConfigTracker)
62-
mock_tracker.track_metrics_of_async = AsyncMock(return_value=_make_model_response('hi'))
69+
mock_tracker.track_metrics_of_async = AsyncMock(return_value=_make_runner_result('hi'))
6370
mock_tracker.get_summary = MagicMock(return_value=_make_summary())
6471
config = AICompletionConfig(
6572
key='test-config',
@@ -96,7 +103,7 @@ async def _slow_evaluate(input_text: str, output_text: str) -> List[JudgeResult]
96103
)
97104

98105
mock_runner = MagicMock()
99-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
106+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result())
100107

101108
config, _tracker = _make_config_with_tracker(evaluator)
102109
model = ManagedModel(config, mock_runner)
@@ -130,7 +137,7 @@ async def _evaluate_coro(input_text: str, output_text: str) -> List[JudgeResult]
130137
)
131138

132139
mock_runner = MagicMock()
133-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
140+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result())
134141

135142
config, _tracker = _make_config_with_tracker(evaluator)
136143
model = ManagedModel(config, mock_runner)
@@ -160,7 +167,7 @@ async def _evaluate_coro(input_text: str, output_text: str) -> List[JudgeResult]
160167
)
161168

162169
mock_runner = MagicMock()
163-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
170+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result())
164171

165172
config, mock_tracker = _make_config_with_tracker(evaluator)
166173
mock_tracker.track_judge_result = MagicMock()
@@ -195,7 +202,7 @@ async def _evaluate_coro(input_text: str, output_text: str) -> List[JudgeResult]
195202
)
196203

197204
mock_runner = MagicMock()
198-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
205+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result())
199206

200207
config, mock_tracker = _make_config_with_tracker(evaluator)
201208
mock_tracker.track_judge_result = MagicMock()
@@ -212,7 +219,7 @@ async def test_noop_evaluator_returns_empty_list(self):
212219
evaluator = Evaluator.noop()
213220

214221
mock_runner = MagicMock()
215-
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
222+
mock_runner.invoke_model = AsyncMock(return_value=_make_runner_result())
216223

217224
config, _tracker = _make_config_with_tracker(evaluator)
218225
model = ManagedModel(config, mock_runner)
@@ -232,7 +239,9 @@ async def test_invoke_emits_deprecation_warning(self):
232239
mock_runner = MagicMock()
233240
mock_runner.invoke_model = AsyncMock(return_value=_make_model_response())
234241

235-
config, _tracker = _make_config_with_tracker(evaluator)
242+
config, mock_tracker = _make_config_with_tracker(evaluator)
243+
# invoke() expects a ModelResponse from the tracker, not a RunnerResult.
244+
mock_tracker.track_metrics_of_async = AsyncMock(return_value=_make_model_response())
236245
model = ManagedModel(config, mock_runner)
237246

238247
with pytest.warns(DeprecationWarning, match=r"ManagedModel\.invoke\(\) is deprecated"):

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
import pytest
22

3-
from ldai.providers import AgentGraphResult, AgentGraphRunner, AgentResult, AgentRunner, ToolRegistry
4-
from ldai.providers.types import LDAIMetrics
3+
from ldai.providers import AgentGraphResult, AgentGraphRunner, AgentRunner, ToolRegistry
4+
from ldai.providers.types import LDAIMetrics, RunnerResult
55

66

77
# --- Concrete test doubles ---
88

99
class ConcreteAgentRunner:
1010
async def run(self, input):
11-
return AgentResult(
12-
output=f"agent response to: {input}",
13-
raw={"raw": input},
11+
return RunnerResult(
12+
content=f"agent response to: {input}",
1413
metrics=LDAIMetrics(success=True),
14+
raw={"raw": input},
1515
)
1616

1717

@@ -39,20 +39,20 @@ def test_agent_runner_structural_check_fails_when_run_missing():
3939

4040

4141
@pytest.mark.asyncio
42-
async def test_agent_runner_run_returns_agent_result():
42+
async def test_agent_runner_run_returns_runner_result():
4343
runner = ConcreteAgentRunner()
4444
result = await runner.run("hello")
45-
assert isinstance(result, AgentResult)
46-
assert result.output == "agent response to: hello"
45+
assert isinstance(result, RunnerResult)
46+
assert result.content == "agent response to: hello"
4747
assert result.raw == {"raw": "hello"}
4848
assert result.metrics.success is True
4949

5050

5151
@pytest.mark.asyncio
52-
async def test_agent_result_fields():
52+
async def test_runner_result_fields():
5353
metrics = LDAIMetrics(success=True)
54-
result = AgentResult(output="done", raw={"key": "val"}, metrics=metrics)
55-
assert result.output == "done"
54+
result = RunnerResult(content="done", metrics=metrics, raw={"key": "val"})
55+
assert result.content == "done"
5656
assert result.raw == {"key": "val"}
5757
assert result.metrics is metrics
5858

@@ -103,6 +103,6 @@ def test_top_level_exports():
103103
import ldai
104104
assert hasattr(ldai, 'AgentRunner')
105105
assert hasattr(ldai, 'AgentGraphRunner')
106-
assert hasattr(ldai, 'AgentResult')
107106
assert hasattr(ldai, 'AgentGraphResult')
107+
assert hasattr(ldai, 'RunnerResult')
108108
assert hasattr(ldai, 'ToolRegistry')

0 commit comments

Comments
 (0)