Skip to content
Merged
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
12 changes: 12 additions & 0 deletions packages/sdk/server-ai/src/ldai/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,23 @@
ModelConfig,
ProviderConfig,
)
from ldai.providers import (
AgentGraphResult,
AgentGraphRunner,
AgentResult,
AgentRunner,
ToolRegistry,
)
from ldai.providers.types import EvalScore, JudgeResponse
from ldai.tracker import AIGraphTracker

__all__ = [
'LDAIClient',
'AgentRunner',
'AgentGraphRunner',
'AgentResult',
'AgentGraphResult',
'ToolRegistry',
'AIAgentConfig',
'AIAgentConfigDefault',
'AIAgentConfigRequest',
Expand Down
22 changes: 22 additions & 0 deletions packages/sdk/server-ai/src/ldai/providers/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,31 @@
from ldai.providers.agent_graph_runner import AgentGraphRunner
from ldai.providers.agent_runner import AgentRunner
from ldai.providers.ai_provider import AIProvider
from ldai.providers.model_runner import ModelRunner
from ldai.providers.runner_factory import RunnerFactory
from ldai.providers.types import (
AgentGraphResult,
AgentResult,
EvalScore,
JudgeResponse,
LDAIMetrics,
ModelResponse,
StructuredResponse,
ToolRegistry,
)

__all__ = [
'AIProvider',
'AgentGraphResult',
'AgentGraphRunner',
'AgentResult',
'AgentRunner',
'EvalScore',
'JudgeResponse',
'LDAIMetrics',
'ModelResponse',
'ModelRunner',
'RunnerFactory',
'StructuredResponse',
'ToolRegistry',
]
23 changes: 23 additions & 0 deletions packages/sdk/server-ai/src/ldai/providers/agent_graph_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Any, Protocol, runtime_checkable

from ldai.providers.types import AgentGraphResult


@runtime_checkable
class AgentGraphRunner(Protocol):
"""
Runtime capability interface for multi-agent graph execution.

An AgentGraphRunner is a focused, configured object returned by
AIProvider.create_agent_graph(). It holds all provider wiring internally —
the caller just passes input.
"""

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
"""
...
23 changes: 23 additions & 0 deletions packages/sdk/server-ai/src/ldai/providers/agent_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from typing import Any, Protocol, runtime_checkable

from ldai.providers.types import AgentResult


@runtime_checkable
class AgentRunner(Protocol):
"""
Runtime capability interface for single-agent execution.

An AgentRunner is a focused, configured object returned by
AIProvider.create_agent(). It holds all provider wiring internally —
the caller just passes input.
"""

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
"""
...
12 changes: 6 additions & 6 deletions packages/sdk/server-ai/src/ldai/providers/model_runner.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
from abc import ABC, abstractmethod
from typing import Any, Dict, List
from typing import Any, Dict, List, Protocol, runtime_checkable

from ldai.models import LDMessage
from ldai.providers.types import ModelResponse, StructuredResponse


class ModelRunner(ABC):
@runtime_checkable
class ModelRunner(Protocol):
"""
Runtime capability interface for model invocation.

A ModelRunner is a focused, configured object returned by
AIConnector.create_model(). It knows exactly which model to call
AIProvider.create_model(). It knows exactly which model to call
and with what parameters — the caller just passes messages.
"""

@abstractmethod
async def invoke_model(self, messages: List[LDMessage]) -> ModelResponse:
"""
Invoke the model with an array of messages.

:param messages: Array of LDMessage objects representing the conversation
:return: ModelResponse containing the model's response and metrics
"""
...

@abstractmethod
async def invoke_structured_model(
self,
messages: List[LDMessage],
Expand All @@ -36,3 +35,4 @@ async def invoke_structured_model(
:param response_structure: Dictionary defining the JSON schema for output structure
:return: StructuredResponse containing the structured data
"""
...
26 changes: 25 additions & 1 deletion packages/sdk/server-ai/src/ldai/providers/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,15 @@
from __future__ import annotations

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

from ldai.models import LDMessage
from ldai.tracker import TokenUsage

# 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 LDAIMetrics:
Expand Down Expand Up @@ -94,3 +98,23 @@ def to_dict(self) -> Dict[str, Any]:
if self.error is not None:
result['error'] = self.error
return result


@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
108 changes: 108 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,108 @@
import pytest

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


# --- Concrete test doubles ---

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


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


class MissingRunMethod:
pass


# --- AgentRunner ---

def test_agent_runner_structural_check_passes():
assert isinstance(ConcreteAgentRunner(), AgentRunner)


def test_agent_runner_structural_check_fails_when_run_missing():
assert not isinstance(MissingRunMethod(), AgentRunner)


@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_structural_check_passes():
assert isinstance(ConcreteAgentGraphRunner(), AgentGraphRunner)


def test_agent_graph_runner_structural_check_fails_when_run_missing():
assert not isinstance(MissingRunMethod(), AgentGraphRunner)


@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