Skip to content

Commit 85f798f

Browse files
authored
feat(sdk): added scorer classes to sdk (#698)
* added scorer class (kept create and list as static methods for now since we don't know how we're creating scorers yet) * refactored static methods to ScorerOps class * fix example docstrings to use correct scorer create params * scorer tests * fixed scorer unit test parameters for update and validate * update scorer and scorer ops docstrings to be more helpful while not exposing system internals * update docs with scorer classes, methods and types * remove verbose request options in unit test parameters * rename client to ops in client test * rename client test file to ops * added list_empty, list_single and list_multiple unit tests to all ops class tests * fix assert_called to assert_awaited * remove duplicate tests
1 parent 6e63928 commit 85f798f

22 files changed

Lines changed: 1259 additions & 148 deletions

docs/sdk/async/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ Asynchronous resource classes for working with devboxes, blueprints, snapshots,
2626
blueprint
2727
snapshot
2828
storage_object
29+
scorer
2930

docs/sdk/async/scorer.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Scorer
2+
======
3+
4+
The ``AsyncScorer`` class provides asynchronous methods for managing custom scenario scorers.
5+
6+
.. automodule:: runloop_api_client.sdk.async_scorer
7+
:members:
8+
9+

docs/sdk/sync/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,5 @@ Synchronous resource classes for working with devboxes, blueprints, snapshots, a
2626
blueprint
2727
snapshot
2828
storage_object
29+
scorer
2930

docs/sdk/sync/scorer.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Scorer
2+
======
3+
4+
The ``Scorer`` class provides synchronous methods for managing custom scenario scorers.
5+
6+
.. automodule:: runloop_api_client.sdk.scorer
7+
:members:
8+
9+

docs/sdk/types.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,19 @@ These TypeDicts define parameters for storage object creation, listing, and down
7878

7979
.. autotypeddict:: runloop_api_client.sdk._types.SDKObjectDownloadParams
8080

81+
Scorer Parameters
82+
-----------------
83+
84+
These TypeDicts define parameters for scorer creation, listing, updating, and validation.
85+
86+
.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerCreateParams
87+
88+
.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerListParams
89+
90+
.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerUpdateParams
91+
92+
.. autotypeddict:: runloop_api_client.sdk._types.SDKScorerValidateParams
93+
8194
Core Request Options
8295
--------------------
8396

src/runloop_api_client/sdk/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,22 @@
55

66
from __future__ import annotations
77

8-
from .sync import DevboxOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps
8+
from .sync import DevboxOps, ScorerOps, RunloopSDK, SnapshotOps, BlueprintOps, StorageObjectOps
99
from .async_ import (
1010
AsyncDevboxOps,
11+
AsyncScorerOps,
1112
AsyncRunloopSDK,
1213
AsyncSnapshotOps,
1314
AsyncBlueprintOps,
1415
AsyncStorageObjectOps,
1516
)
1617
from .devbox import Devbox, NamedShell
18+
from .scorer import Scorer
1719
from .snapshot import Snapshot
1820
from .blueprint import Blueprint
1921
from .execution import Execution
2022
from .async_devbox import AsyncDevbox, AsyncNamedShell
23+
from .async_scorer import AsyncScorer
2124
from .async_snapshot import AsyncSnapshot
2225
from .storage_object import StorageObject
2326
from .async_blueprint import AsyncBlueprint
@@ -35,6 +38,8 @@
3538
"AsyncDevboxOps",
3639
"BlueprintOps",
3740
"AsyncBlueprintOps",
41+
"ScorerOps",
42+
"AsyncScorerOps",
3843
"SnapshotOps",
3944
"AsyncSnapshotOps",
4045
"StorageObjectOps",
@@ -48,6 +53,8 @@
4853
"AsyncExecutionResult",
4954
"Blueprint",
5055
"AsyncBlueprint",
56+
"Scorer",
57+
"AsyncScorer",
5158
"Snapshot",
5259
"AsyncSnapshot",
5360
"StorageObject",

src/runloop_api_client/sdk/_types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from .._types import Body, Query, Headers, Timeout, NotGiven
55
from ..lib.polling import PollingConfig
66
from ..types.devboxes import DiskSnapshotListParams, DiskSnapshotUpdateParams
7+
from ..types.scenarios import ScorerListParams, ScorerCreateParams, ScorerUpdateParams, ScorerValidateParams
78
from ..types.devbox_list_params import DevboxListParams
89
from ..types.object_list_params import ObjectListParams
910
from ..types.devbox_create_params import DevboxCreateParams, DevboxBaseCreateParams
@@ -140,3 +141,19 @@ class SDKObjectCreateParams(ObjectCreateParams, LongRequestOptions):
140141

141142
class SDKObjectDownloadParams(ObjectDownloadParams, BaseRequestOptions):
142143
pass
144+
145+
146+
class SDKScorerCreateParams(ScorerCreateParams, LongRequestOptions):
147+
pass
148+
149+
150+
class SDKScorerListParams(ScorerListParams, BaseRequestOptions):
151+
pass
152+
153+
154+
class SDKScorerUpdateParams(ScorerUpdateParams, LongRequestOptions):
155+
pass
156+
157+
158+
class SDKScorerValidateParams(ScorerValidateParams, LongRequestOptions):
159+
pass

src/runloop_api_client/sdk/async_.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
LongRequestOptions,
1717
SDKDevboxListParams,
1818
SDKObjectListParams,
19+
SDKScorerListParams,
1920
SDKDevboxCreateParams,
2021
SDKObjectCreateParams,
22+
SDKScorerCreateParams,
2123
SDKBlueprintListParams,
2224
SDKBlueprintCreateParams,
2325
SDKDiskSnapshotListParams,
@@ -27,6 +29,7 @@
2729
from .._client import DEFAULT_MAX_RETRIES, AsyncRunloop
2830
from ._helpers import detect_content_type
2931
from .async_devbox import AsyncDevbox
32+
from .async_scorer import AsyncScorer
3033
from .async_snapshot import AsyncSnapshot
3134
from .async_blueprint import AsyncBlueprint
3235
from .async_storage_object import AsyncStorageObject
@@ -492,6 +495,54 @@ async def upload_from_bytes(
492495
return obj
493496

494497

498+
class AsyncScorerOps:
499+
"""Create and manage custom scorers (async). Access via ``runloop.scorer``.
500+
501+
Example:
502+
>>> runloop = AsyncRunloopSDK()
503+
>>> scorer = await runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
504+
>>> all_scorers = await runloop.scorer.list()
505+
"""
506+
507+
def __init__(self, client: AsyncRunloop) -> None:
508+
"""Initialize AsyncScorerOps.
509+
510+
:param client: AsyncRunloop client instance
511+
:type client: AsyncRunloop
512+
"""
513+
self._client = client
514+
515+
async def create(self, **params: Unpack[SDKScorerCreateParams]) -> AsyncScorer:
516+
"""Create a new scorer with the given type and bash script.
517+
518+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerCreateParams` for available parameters
519+
:return: The newly created scorer
520+
:rtype: AsyncScorer
521+
"""
522+
response = await self._client.scenarios.scorers.create(**params)
523+
return AsyncScorer(self._client, response.id)
524+
525+
def from_id(self, scorer_id: str) -> AsyncScorer:
526+
"""Get an AsyncScorer instance for an existing scorer ID.
527+
528+
:param scorer_id: ID of the scorer
529+
:type scorer_id: str
530+
:return: AsyncScorer instance for the given ID
531+
:rtype: AsyncScorer
532+
"""
533+
return AsyncScorer(self._client, scorer_id)
534+
535+
async def list(self, **params: Unpack[SDKScorerListParams]) -> list[AsyncScorer]:
536+
"""List all scorers, optionally filtered by parameters.
537+
538+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerListParams` for available parameters
539+
:return: List of scorers
540+
:rtype: list[AsyncScorer]
541+
"""
542+
page = await self._client.scenarios.scorers.list(**params)
543+
return [AsyncScorer(self._client, item.id) async for item in page]
544+
545+
495546
class AsyncRunloopSDK:
496547
"""High-level asynchronous entry point for the Runloop SDK.
497548
@@ -505,6 +556,8 @@ class AsyncRunloopSDK:
505556
:vartype devbox: AsyncDevboxOps
506557
:ivar blueprint: High-level async interface for blueprint management
507558
:vartype blueprint: AsyncBlueprintOps
559+
:ivar scorer: High-level async interface for scorer management
560+
:vartype scorer: AsyncScorerOps
508561
:ivar snapshot: High-level async interface for snapshot management
509562
:vartype snapshot: AsyncSnapshotOps
510563
:ivar storage_object: High-level async interface for storage object management
@@ -521,6 +574,7 @@ class AsyncRunloopSDK:
521574
api: AsyncRunloop
522575
devbox: AsyncDevboxOps
523576
blueprint: AsyncBlueprintOps
577+
scorer: AsyncScorerOps
524578
snapshot: AsyncSnapshotOps
525579
storage_object: AsyncStorageObjectOps
526580

@@ -564,6 +618,7 @@ def __init__(
564618

565619
self.devbox = AsyncDevboxOps(self.api)
566620
self.blueprint = AsyncBlueprintOps(self.api)
621+
self.scorer = AsyncScorerOps(self.api)
567622
self.snapshot = AsyncSnapshotOps(self.api)
568623
self.storage_object = AsyncStorageObjectOps(self.api)
569624

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Scorer resource class for asynchronous operations."""
2+
3+
from __future__ import annotations
4+
5+
from typing_extensions import Unpack, override
6+
7+
from ._types import (
8+
BaseRequestOptions,
9+
SDKScorerUpdateParams,
10+
SDKScorerValidateParams,
11+
)
12+
from .._client import AsyncRunloop
13+
from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse, ScorerValidateResponse
14+
15+
16+
class AsyncScorer:
17+
"""A custom scorer for evaluating scenario outputs (async).
18+
19+
Scorers define bash scripts that produce a score (0.0-1.0) for scenario runs.
20+
Obtain instances via ``runloop.scorer.create()`` or ``runloop.scorer.from_id()``.
21+
22+
Example:
23+
>>> runloop = AsyncRunloopSDK()
24+
>>> scorer = await runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
25+
>>> await scorer.validate(scoring_context={"output": "test"})
26+
"""
27+
28+
def __init__(self, client: AsyncRunloop, scorer_id: str) -> None:
29+
"""Create an AsyncScorer instance.
30+
31+
:param client: AsyncRunloop client instance
32+
:type client: AsyncRunloop
33+
:param scorer_id: ID of the scorer
34+
:type scorer_id: str
35+
"""
36+
self._client = client
37+
self._id = scorer_id
38+
39+
@override
40+
def __repr__(self) -> str:
41+
return f"<AsyncScorer id={self._id!r}>"
42+
43+
@property
44+
def id(self) -> str:
45+
"""The scorer's unique identifier.
46+
47+
:return: Scorer ID
48+
:rtype: str
49+
"""
50+
return self._id
51+
52+
async def get_info(self, **options: Unpack[BaseRequestOptions]) -> ScorerRetrieveResponse:
53+
"""Fetch current scorer details from the API.
54+
55+
:param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
56+
:return: Current scorer details
57+
:rtype: ScorerRetrieveResponse
58+
"""
59+
return await self._client.scenarios.scorers.retrieve(self._id, **options)
60+
61+
async def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateResponse:
62+
"""Update the scorer's type or bash script.
63+
64+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerUpdateParams` for available parameters
65+
:return: Updated scorer details
66+
:rtype: ScorerUpdateResponse
67+
"""
68+
return await self._client.scenarios.scorers.update(self._id, **params)
69+
70+
async def validate(self, **params: Unpack[SDKScorerValidateParams]) -> ScorerValidateResponse:
71+
"""Run the scorer against the provided context and return the result.
72+
73+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerValidateParams` for available parameters
74+
:return: Validation result with score
75+
:rtype: ScorerValidateResponse
76+
"""
77+
return await self._client.scenarios.scorers.validate(self._id, **params)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
"""Scorer resource class for synchronous operations."""
2+
3+
from __future__ import annotations
4+
5+
from typing_extensions import Unpack, override
6+
7+
from ._types import (
8+
BaseRequestOptions,
9+
SDKScorerUpdateParams,
10+
SDKScorerValidateParams,
11+
)
12+
from .._client import Runloop
13+
from ..types.scenarios import ScorerUpdateResponse, ScorerRetrieveResponse, ScorerValidateResponse
14+
15+
16+
class Scorer:
17+
"""A custom scorer for evaluating scenario outputs.
18+
19+
Scorers define bash scripts that produce a score (0.0-1.0) for scenario runs.
20+
Obtain instances via ``runloop.scorer.create()`` or ``runloop.scorer.from_id()``.
21+
22+
Example:
23+
>>> runloop = RunloopSDK()
24+
>>> scorer = runloop.scorer.create(type="my_scorer", bash_script="echo 'score=1.0'")
25+
>>> scorer.validate(scoring_context={"output": "test"})
26+
"""
27+
28+
def __init__(self, client: Runloop, scorer_id: str) -> None:
29+
"""Create a Scorer instance.
30+
31+
:param client: Runloop client instance
32+
:type client: Runloop
33+
:param scorer_id: ID of the scorer
34+
:type scorer_id: str
35+
"""
36+
self._client = client
37+
self._id = scorer_id
38+
39+
@override
40+
def __repr__(self) -> str:
41+
return f"<Scorer id={self._id!r}>"
42+
43+
@property
44+
def id(self) -> str:
45+
"""The scorer's unique identifier.
46+
47+
:return: Scorer ID
48+
:rtype: str
49+
"""
50+
return self._id
51+
52+
def get_info(self, **options: Unpack[BaseRequestOptions]) -> ScorerRetrieveResponse:
53+
"""Fetch current scorer details from the API.
54+
55+
:param options: See :typeddict:`~runloop_api_client.sdk._types.BaseRequestOptions` for available options
56+
:return: Current scorer details
57+
:rtype: ScorerRetrieveResponse
58+
"""
59+
return self._client.scenarios.scorers.retrieve(self._id, **options)
60+
61+
def update(self, **params: Unpack[SDKScorerUpdateParams]) -> ScorerUpdateResponse:
62+
"""Update the scorer's type or bash script.
63+
64+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerUpdateParams` for available parameters
65+
:return: Updated scorer details
66+
:rtype: ScorerUpdateResponse
67+
"""
68+
return self._client.scenarios.scorers.update(self._id, **params)
69+
70+
def validate(self, **params: Unpack[SDKScorerValidateParams]) -> ScorerValidateResponse:
71+
"""Run the scorer against the provided context and return the result.
72+
73+
:param params: See :typeddict:`~runloop_api_client.sdk._types.SDKScorerValidateParams` for available parameters
74+
:return: Validation result with score
75+
:rtype: ScorerValidateResponse
76+
"""
77+
return self._client.scenarios.scorers.validate(self._id, **params)

0 commit comments

Comments
 (0)