Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/sdk/server-ai/src/ldai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,17 @@
ProviderConfig,
)
from ldai.providers.types import EvalScore, JudgeResponse
from ldai.runners import AgentGraphRunner, AgentRunner
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry
from ldai.tracker import AIGraphTracker

__all__ = [
'LDAIClient',
'AgentRunner',
'AgentGraphRunner',
'AgentResult',
'AgentGraphResult',
'ToolRegistry',
'AIAgentConfig',
'AIAgentConfigDefault',
'AIAgentConfigRequest',
Expand Down
13 changes: 13 additions & 0 deletions packages/sdk/server-ai/src/ldai/runners/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""Runner ABCs and result types for LaunchDarkly AI SDK."""

from ldai.runners.agent_graph_runner import AgentGraphRunner
from ldai.runners.agent_runner import AgentRunner
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry

__all__ = [
'AgentRunner',
'AgentGraphRunner',
'AgentResult',
'AgentGraphResult',
'ToolRegistry',
]
26 changes: 26 additions & 0 deletions packages/sdk/server-ai/src/ldai/runners/agent_graph_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"""Abstract base class for agent graph runners."""

from abc import ABC, abstractmethod
from typing import Any

from ldai.runners.types import AgentGraphResult


class AgentGraphRunner(ABC):
"""
Abstract base class for agent graph runners.

An AgentGraphRunner encapsulates multi-agent graph execution.
Provider-specific implementations (e.g. OpenAIAgentGraphRunner) are
returned by RunnerFactory.create_agent_graph() and hold all provider
wiring internally.
"""

@abstractmethod
async def run(self, input: Any) -> AgentGraphResult:
"""
Run the agent graph with the given input.

:param input: The input to the agent graph (string prompt or structured input)
:return: AgentGraphResult containing the output, raw response, and metrics
"""
25 changes: 25 additions & 0 deletions packages/sdk/server-ai/src/ldai/runners/agent_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"""Abstract base class for agent runners."""

from abc import ABC, abstractmethod
from typing import Any

from ldai.runners.types import AgentResult


class AgentRunner(ABC):
"""
Abstract base class for single-agent runners.

An AgentRunner encapsulates the execution of a single AI agent.
Provider-specific implementations (e.g. OpenAIAgentRunner) are returned
by RunnerFactory.create_agent() and hold all provider wiring internally.
"""

@abstractmethod
async def run(self, input: Any) -> AgentResult:
"""
Run the agent with the given input.

:param input: The input to the agent (string prompt or structured input)
:return: AgentResult containing the output, raw response, and metrics
"""
32 changes: 32 additions & 0 deletions packages/sdk/server-ai/src/ldai/runners/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Result types and type aliases for agent and agent graph runners."""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Callable, Dict

from ldai.providers.types import LDAIMetrics

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


@dataclass
class AgentResult:
"""
Result from a single-agent run.
"""
output: str
raw: Any
metrics: LDAIMetrics


@dataclass
class AgentGraphResult:
"""
Result from an agent graph run.
"""
output: str
raw: Any
metrics: LDAIMetrics
100 changes: 100 additions & 0 deletions packages/sdk/server-ai/tests/test_runner_abcs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import pytest

from ldai.providers.types import LDAIMetrics
from ldai.runners.agent_graph_runner import AgentGraphRunner
from ldai.runners.agent_runner import AgentRunner
from ldai.runners.types import AgentGraphResult, AgentResult, ToolRegistry


# --- Concrete test doubles ---

class ConcreteAgentRunner(AgentRunner):
async def run(self, input):
return AgentResult(
output=f"agent response to: {input}",
raw={"raw": input},
metrics=LDAIMetrics(success=True),
)


class ConcreteAgentGraphRunner(AgentGraphRunner):
async def run(self, input):
return AgentGraphResult(
output=f"graph response to: {input}",
raw={"raw": input},
metrics=LDAIMetrics(success=True),
)


# --- AgentRunner ---

def test_agent_runner_is_abstract():
with pytest.raises(TypeError):
AgentRunner() # type: ignore[abstract]


@pytest.mark.asyncio
async def test_agent_runner_run_returns_agent_result():
runner = ConcreteAgentRunner()
result = await runner.run("hello")
assert isinstance(result, AgentResult)
assert result.output == "agent response to: hello"
assert result.raw == {"raw": "hello"}
assert result.metrics.success is True


@pytest.mark.asyncio
async def test_agent_result_fields():
metrics = LDAIMetrics(success=True)
result = AgentResult(output="done", raw={"key": "val"}, metrics=metrics)
assert result.output == "done"
assert result.raw == {"key": "val"}
assert result.metrics is metrics


# --- AgentGraphRunner ---

def test_agent_graph_runner_is_abstract():
with pytest.raises(TypeError):
AgentGraphRunner() # type: ignore[abstract]


@pytest.mark.asyncio
async def test_agent_graph_runner_run_returns_agent_graph_result():
runner = ConcreteAgentGraphRunner()
result = await runner.run("hello graph")
assert isinstance(result, AgentGraphResult)
assert result.output == "graph response to: hello graph"
assert result.raw == {"raw": "hello graph"}
assert result.metrics.success is True


@pytest.mark.asyncio
async def test_agent_graph_result_fields():
metrics = LDAIMetrics(success=False)
result = AgentGraphResult(output="", raw=None, metrics=metrics)
assert result.output == ""
assert result.raw is None
assert result.metrics.success is False


# --- ToolRegistry ---

def test_tool_registry_is_dict_of_callables():
tools: ToolRegistry = {
"search": lambda q: f"results for {q}",
"calculator": lambda x: x * 2,
}
assert tools["search"]("python") == "results for python"
assert tools["calculator"](21) == 42


# --- Top-level exports ---

def test_top_level_exports():
import ldai
assert hasattr(ldai, 'AgentRunner')
assert hasattr(ldai, 'AgentGraphRunner')
assert hasattr(ldai, 'AgentResult')
assert hasattr(ldai, 'AgentGraphResult')
assert hasattr(ldai, 'ToolRegistry')
Loading