Skip to content

Commit db27c82

Browse files
jsonbaileyclaude
andcommitted
feat: add AgentRunner, AgentGraphRunner ABCs and result types
Adds the runner ABCs and result types needed to support agent and agent graph execution in later PRs: - ldai/runners/agent_runner.py: AgentRunner ABC with run(input) -> AgentResult - ldai/runners/agent_graph_runner.py: AgentGraphRunner ABC with run(input) -> AgentGraphResult - ldai/runners/types.py: AgentResult, AgentGraphResult (output, raw, metrics), ToolRegistry alias - Exports all new types from ldai top-level __init__.py Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 4bef65c commit db27c82

6 files changed

Lines changed: 203 additions & 0 deletions

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,17 @@
2727
ProviderConfig,
2828
)
2929
from ldai.providers.types import EvalScore, JudgeResponse
30+
from ldai.runners import AgentGraphRunner, AgentRunner
31+
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry
3032
from ldai.tracker import AIGraphTracker
3133

3234
__all__ = [
3335
'LDAIClient',
36+
'AgentRunner',
37+
'AgentGraphRunner',
38+
'AgentResult',
39+
'AgentGraphResult',
40+
'ToolRegistry',
3441
'AIAgentConfig',
3542
'AIAgentConfigDefault',
3643
'AIAgentConfigRequest',
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"""Runner ABCs and result types for LaunchDarkly AI SDK."""
2+
3+
from ldai.runners.agent_graph_runner import AgentGraphRunner
4+
from ldai.runners.agent_runner import AgentRunner
5+
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry
6+
7+
__all__ = [
8+
'AgentRunner',
9+
'AgentGraphRunner',
10+
'AgentResult',
11+
'AgentGraphResult',
12+
'ToolRegistry',
13+
]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""Abstract base class for agent graph runners."""
2+
3+
from abc import ABC, abstractmethod
4+
from typing import Any
5+
6+
from ldai.runners.types import AgentGraphResult
7+
8+
9+
class AgentGraphRunner(ABC):
10+
"""
11+
Abstract base class for agent graph runners.
12+
13+
An AgentGraphRunner encapsulates multi-agent graph execution.
14+
Provider-specific implementations (e.g. OpenAIAgentGraphRunner) are
15+
returned by RunnerFactory.create_agent_graph() and hold all provider
16+
wiring internally.
17+
"""
18+
19+
@abstractmethod
20+
async def run(self, input: Any) -> AgentGraphResult:
21+
"""
22+
Run the agent graph with the given input.
23+
24+
:param input: The input to the agent graph (string prompt or structured input)
25+
:return: AgentGraphResult containing the output, raw response, and metrics
26+
"""
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""Abstract base class for agent runners."""
2+
3+
from abc import ABC, abstractmethod
4+
from typing import Any
5+
6+
from ldai.runners.types import AgentResult
7+
8+
9+
class AgentRunner(ABC):
10+
"""
11+
Abstract base class for single-agent runners.
12+
13+
An AgentRunner encapsulates the execution of a single AI agent.
14+
Provider-specific implementations (e.g. OpenAIAgentRunner) are returned
15+
by RunnerFactory.create_agent() and hold all provider wiring internally.
16+
"""
17+
18+
@abstractmethod
19+
async def run(self, input: Any) -> AgentResult:
20+
"""
21+
Run the agent with the given input.
22+
23+
:param input: The input to the agent (string prompt or structured input)
24+
:return: AgentResult containing the output, raw response, and metrics
25+
"""
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Result types and type aliases for agent and agent graph runners."""
2+
3+
from __future__ import annotations
4+
5+
from dataclasses import dataclass
6+
from typing import Any, Callable, Dict
7+
8+
from ldai.providers.types import LDAIMetrics
9+
10+
# Type alias for a registry of tools available to an agent.
11+
# Keys are tool names; values are the callable implementations.
12+
ToolRegistry = Dict[str, Callable]
13+
14+
15+
@dataclass
16+
class AgentResult:
17+
"""
18+
Result from a single-agent run.
19+
"""
20+
output: str
21+
raw: Any
22+
metrics: LDAIMetrics
23+
24+
25+
@dataclass
26+
class AgentGraphResult:
27+
"""
28+
Result from an agent graph run.
29+
"""
30+
output: str
31+
raw: Any
32+
metrics: LDAIMetrics
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import pytest
2+
3+
from ldai.providers.types import LDAIMetrics
4+
from ldai.runners.agent_graph_runner import AgentGraphRunner
5+
from ldai.runners.agent_runner import AgentRunner
6+
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry
7+
8+
9+
# --- Concrete test doubles ---
10+
11+
class ConcreteAgentRunner(AgentRunner):
12+
async def run(self, input):
13+
return AgentResult(
14+
output=f"agent response to: {input}",
15+
raw={"raw": input},
16+
metrics=LDAIMetrics(success=True),
17+
)
18+
19+
20+
class ConcreteAgentGraphRunner(AgentGraphRunner):
21+
async def run(self, input):
22+
return AgentGraphResult(
23+
output=f"graph response to: {input}",
24+
raw={"raw": input},
25+
metrics=LDAIMetrics(success=True),
26+
)
27+
28+
29+
# --- AgentRunner ---
30+
31+
def test_agent_runner_is_abstract():
32+
with pytest.raises(TypeError):
33+
AgentRunner() # type: ignore[abstract]
34+
35+
36+
@pytest.mark.asyncio
37+
async def test_agent_runner_run_returns_agent_result():
38+
runner = ConcreteAgentRunner()
39+
result = await runner.run("hello")
40+
assert isinstance(result, AgentResult)
41+
assert result.output == "agent response to: hello"
42+
assert result.raw == {"raw": "hello"}
43+
assert result.metrics.success is True
44+
45+
46+
@pytest.mark.asyncio
47+
async def test_agent_result_fields():
48+
metrics = LDAIMetrics(success=True)
49+
result = AgentResult(output="done", raw={"key": "val"}, metrics=metrics)
50+
assert result.output == "done"
51+
assert result.raw == {"key": "val"}
52+
assert result.metrics is metrics
53+
54+
55+
# --- AgentGraphRunner ---
56+
57+
def test_agent_graph_runner_is_abstract():
58+
with pytest.raises(TypeError):
59+
AgentGraphRunner() # type: ignore[abstract]
60+
61+
62+
@pytest.mark.asyncio
63+
async def test_agent_graph_runner_run_returns_agent_graph_result():
64+
runner = ConcreteAgentGraphRunner()
65+
result = await runner.run("hello graph")
66+
assert isinstance(result, AgentGraphResult)
67+
assert result.output == "graph response to: hello graph"
68+
assert result.raw == {"raw": "hello graph"}
69+
assert result.metrics.success is True
70+
71+
72+
@pytest.mark.asyncio
73+
async def test_agent_graph_result_fields():
74+
metrics = LDAIMetrics(success=False)
75+
result = AgentGraphResult(output="", raw=None, metrics=metrics)
76+
assert result.output == ""
77+
assert result.raw is None
78+
assert result.metrics.success is False
79+
80+
81+
# --- ToolRegistry ---
82+
83+
def test_tool_registry_is_dict_of_callables():
84+
tools: ToolRegistry = {
85+
"search": lambda q: f"results for {q}",
86+
"calculator": lambda x: x * 2,
87+
}
88+
assert tools["search"]("python") == "results for python"
89+
assert tools["calculator"](21) == 42
90+
91+
92+
# --- Top-level exports ---
93+
94+
def test_top_level_exports():
95+
import ldai
96+
assert hasattr(ldai, 'AgentRunner')
97+
assert hasattr(ldai, 'AgentGraphRunner')
98+
assert hasattr(ldai, 'AgentResult')
99+
assert hasattr(ldai, 'AgentGraphResult')
100+
assert hasattr(ldai, 'ToolRegistry')

0 commit comments

Comments
 (0)