Skip to content

Commit 9b6f06a

Browse files
authored
chore: add AgentRunner, AgentGraphRunner Protocols and result types (#103)
1 parent 453c71c commit 9b6f06a

7 files changed

Lines changed: 219 additions & 7 deletions

File tree

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,23 @@
2727
ModelConfig,
2828
ProviderConfig,
2929
)
30+
from ldai.providers import (
31+
AgentGraphResult,
32+
AgentGraphRunner,
33+
AgentResult,
34+
AgentRunner,
35+
ToolRegistry,
36+
)
3037
from ldai.providers.types import EvalScore, JudgeResponse
3138
from ldai.tracker import AIGraphTracker
3239

3340
__all__ = [
3441
'LDAIClient',
42+
'AgentRunner',
43+
'AgentGraphRunner',
44+
'AgentResult',
45+
'AgentGraphResult',
46+
'ToolRegistry',
3547
'AIAgentConfig',
3648
'AIAgentConfigDefault',
3749
'AIAgentConfigRequest',
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,31 @@
1+
from ldai.providers.agent_graph_runner import AgentGraphRunner
2+
from ldai.providers.agent_runner import AgentRunner
13
from ldai.providers.ai_provider import AIProvider
24
from ldai.providers.model_runner import ModelRunner
35
from ldai.providers.runner_factory import RunnerFactory
6+
from ldai.providers.types import (
7+
AgentGraphResult,
8+
AgentResult,
9+
EvalScore,
10+
JudgeResponse,
11+
LDAIMetrics,
12+
ModelResponse,
13+
StructuredResponse,
14+
ToolRegistry,
15+
)
416

517
__all__ = [
618
'AIProvider',
19+
'AgentGraphResult',
20+
'AgentGraphRunner',
21+
'AgentResult',
22+
'AgentRunner',
23+
'EvalScore',
24+
'JudgeResponse',
25+
'LDAIMetrics',
26+
'ModelResponse',
727
'ModelRunner',
828
'RunnerFactory',
29+
'StructuredResponse',
30+
'ToolRegistry',
931
]
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Any, Protocol, runtime_checkable
2+
3+
from ldai.providers.types import AgentGraphResult
4+
5+
6+
@runtime_checkable
7+
class AgentGraphRunner(Protocol):
8+
"""
9+
Runtime capability interface for multi-agent graph execution.
10+
11+
An AgentGraphRunner is a focused, configured object returned by
12+
AIProvider.create_agent_graph(). It holds all provider wiring internally —
13+
the caller just passes input.
14+
"""
15+
16+
async def run(self, input: Any) -> AgentGraphResult:
17+
"""
18+
Run the agent graph with the given input.
19+
20+
:param input: The input to the agent graph (string prompt or structured input)
21+
:return: AgentGraphResult containing the output, raw response, and metrics
22+
"""
23+
...
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from typing import Any, Protocol, runtime_checkable
2+
3+
from ldai.providers.types import AgentResult
4+
5+
6+
@runtime_checkable
7+
class AgentRunner(Protocol):
8+
"""
9+
Runtime capability interface for single-agent execution.
10+
11+
An AgentRunner is a focused, configured object returned by
12+
AIProvider.create_agent(). It holds all provider wiring internally —
13+
the caller just passes input.
14+
"""
15+
16+
async def run(self, input: Any) -> AgentResult:
17+
"""
18+
Run the agent with the given input.
19+
20+
:param input: The input to the agent (string prompt or structured input)
21+
:return: AgentResult containing the output, raw response, and metrics
22+
"""
23+
...
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,28 @@
1-
from abc import ABC, abstractmethod
2-
from typing import Any, Dict, List
1+
from typing import Any, Dict, List, Protocol, runtime_checkable
32

43
from ldai.models import LDMessage
54
from ldai.providers.types import ModelResponse, StructuredResponse
65

76

8-
class ModelRunner(ABC):
7+
@runtime_checkable
8+
class ModelRunner(Protocol):
99
"""
1010
Runtime capability interface for model invocation.
1111
1212
A ModelRunner is a focused, configured object returned by
13-
AIConnector.create_model(). It knows exactly which model to call
13+
AIProvider.create_model(). It knows exactly which model to call
1414
and with what parameters — the caller just passes messages.
1515
"""
1616

17-
@abstractmethod
1817
async def invoke_model(self, messages: List[LDMessage]) -> ModelResponse:
1918
"""
2019
Invoke the model with an array of messages.
2120
2221
:param messages: Array of LDMessage objects representing the conversation
2322
:return: ModelResponse containing the model's response and metrics
2423
"""
24+
...
2525

26-
@abstractmethod
2726
async def invoke_structured_model(
2827
self,
2928
messages: List[LDMessage],
@@ -36,3 +35,4 @@ async def invoke_structured_model(
3635
:param response_structure: Dictionary defining the JSON schema for output structure
3736
:return: StructuredResponse containing the structured data
3837
"""
38+
...

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
from __future__ import annotations
44

55
from dataclasses import dataclass
6-
from typing import Any, Dict, List, Optional
6+
from typing import Any, Callable, Dict, List, Optional
77

88
from ldai.models import LDMessage
99
from ldai.tracker import TokenUsage
1010

11+
# Type alias for a registry of tools available to an agent.
12+
# Keys are tool names; values are the callable implementations.
13+
ToolRegistry = Dict[str, Callable]
14+
1115

1216
@dataclass
1317
class LDAIMetrics:
@@ -94,3 +98,23 @@ def to_dict(self) -> Dict[str, Any]:
9498
if self.error is not None:
9599
result['error'] = self.error
96100
return result
101+
102+
103+
@dataclass
104+
class AgentResult:
105+
"""
106+
Result from a single-agent run.
107+
"""
108+
output: str
109+
raw: Any
110+
metrics: LDAIMetrics
111+
112+
113+
@dataclass
114+
class AgentGraphResult:
115+
"""
116+
Result from an agent graph run.
117+
"""
118+
output: str
119+
raw: Any
120+
metrics: LDAIMetrics
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import pytest
2+
3+
from ldai.providers import AgentGraphResult, AgentGraphRunner, AgentResult, AgentRunner, ToolRegistry
4+
from ldai.providers.types import LDAIMetrics
5+
6+
7+
# --- Concrete test doubles ---
8+
9+
class ConcreteAgentRunner:
10+
async def run(self, input):
11+
return AgentResult(
12+
output=f"agent response to: {input}",
13+
raw={"raw": input},
14+
metrics=LDAIMetrics(success=True),
15+
)
16+
17+
18+
class ConcreteAgentGraphRunner:
19+
async def run(self, input):
20+
return AgentGraphResult(
21+
output=f"graph response to: {input}",
22+
raw={"raw": input},
23+
metrics=LDAIMetrics(success=True),
24+
)
25+
26+
27+
class MissingRunMethod:
28+
pass
29+
30+
31+
# --- AgentRunner ---
32+
33+
def test_agent_runner_structural_check_passes():
34+
assert isinstance(ConcreteAgentRunner(), AgentRunner)
35+
36+
37+
def test_agent_runner_structural_check_fails_when_run_missing():
38+
assert not isinstance(MissingRunMethod(), AgentRunner)
39+
40+
41+
@pytest.mark.asyncio
42+
async def test_agent_runner_run_returns_agent_result():
43+
runner = ConcreteAgentRunner()
44+
result = await runner.run("hello")
45+
assert isinstance(result, AgentResult)
46+
assert result.output == "agent response to: hello"
47+
assert result.raw == {"raw": "hello"}
48+
assert result.metrics.success is True
49+
50+
51+
@pytest.mark.asyncio
52+
async def test_agent_result_fields():
53+
metrics = LDAIMetrics(success=True)
54+
result = AgentResult(output="done", raw={"key": "val"}, metrics=metrics)
55+
assert result.output == "done"
56+
assert result.raw == {"key": "val"}
57+
assert result.metrics is metrics
58+
59+
60+
# --- AgentGraphRunner ---
61+
62+
def test_agent_graph_runner_structural_check_passes():
63+
assert isinstance(ConcreteAgentGraphRunner(), AgentGraphRunner)
64+
65+
66+
def test_agent_graph_runner_structural_check_fails_when_run_missing():
67+
assert not isinstance(MissingRunMethod(), AgentGraphRunner)
68+
69+
70+
@pytest.mark.asyncio
71+
async def test_agent_graph_runner_run_returns_agent_graph_result():
72+
runner = ConcreteAgentGraphRunner()
73+
result = await runner.run("hello graph")
74+
assert isinstance(result, AgentGraphResult)
75+
assert result.output == "graph response to: hello graph"
76+
assert result.raw == {"raw": "hello graph"}
77+
assert result.metrics.success is True
78+
79+
80+
@pytest.mark.asyncio
81+
async def test_agent_graph_result_fields():
82+
metrics = LDAIMetrics(success=False)
83+
result = AgentGraphResult(output="", raw=None, metrics=metrics)
84+
assert result.output == ""
85+
assert result.raw is None
86+
assert result.metrics.success is False
87+
88+
89+
# --- ToolRegistry ---
90+
91+
def test_tool_registry_is_dict_of_callables():
92+
tools: ToolRegistry = {
93+
"search": lambda q: f"results for {q}",
94+
"calculator": lambda x: x * 2,
95+
}
96+
assert tools["search"]("python") == "results for python"
97+
assert tools["calculator"](21) == 42
98+
99+
100+
# --- Top-level exports ---
101+
102+
def test_top_level_exports():
103+
import ldai
104+
assert hasattr(ldai, 'AgentRunner')
105+
assert hasattr(ldai, 'AgentGraphRunner')
106+
assert hasattr(ldai, 'AgentResult')
107+
assert hasattr(ldai, 'AgentGraphResult')
108+
assert hasattr(ldai, 'ToolRegistry')

0 commit comments

Comments
 (0)