hyperstudy 0.3.1__tar.gz → 0.5.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/CHANGELOG.md +29 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/PKG-INFO +61 -3
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/README.md +60 -2
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/pyproject.toml +1 -1
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/__init__.py +9 -1
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_types.py +3 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/client.py +267 -1
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/experiments.py +11 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/models.py +78 -1
- hyperstudy-0.5.0/src/hyperstudy/personas.py +121 -0
- hyperstudy-0.5.0/tests/fixtures/agent_decision_detail_response.json +30 -0
- hyperstudy-0.5.0/tests/fixtures/agent_decisions_response.json +63 -0
- hyperstudy-0.5.0/tests/fixtures/agent_decisions_truncated_response.json +26 -0
- hyperstudy-0.5.0/tests/fixtures/agent_runs_response.json +46 -0
- hyperstudy-0.5.0/tests/test_agent_data.py +184 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_client.py +129 -3
- hyperstudy-0.5.0/tests/test_deployments_write.py +101 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_experiments.py +32 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_models.py +55 -0
- hyperstudy-0.5.0/tests/test_personas.py +197 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/.github/workflows/publish.yml +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/.github/workflows/sync-release-notes.yml +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/.github/workflows/test.yml +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/.gitignore +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/LICENSE +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/docs/superpowers/specs/2026-04-10-recording-downloads-design.md +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_dataframe.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_display.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_downloads.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_http.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/_pagination.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/src/hyperstudy/exceptions.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/__init__.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/conftest.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/deployment_sessions_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/deployment_single_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/deployments_list_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/error_401.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/error_403.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/events_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/experiment.schema.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/experiment_single_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/experiments_list_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/paginated_page1.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/paginated_page2.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/pre_experiment_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/recordings_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/sparse_ratings_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/fixtures/warnings_response.json +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_dataframe.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_downloads.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_http.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_pagination.py +0 -0
- {hyperstudy-0.3.1 → hyperstudy-0.5.0}/tests/test_recordings_download.py +0 -0
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.5.0
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **Removed `Persona.offline_cognition`.** The server now hard-discards this field (`offlineCognition` is always null server-side); offline loops live inside `cognition.config.contexts.<name>.offline`. The SDK also strips `offlineCognition` from create/update payloads so personas rebuilt from older API responses never send it.
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **Eye-tracking data access**: `get_eyetracking(scope_id, scope="experiment"|"room"|"participant", room_id=)` with the standard pagination/output options, plus `DataType.EYETRACKING`. `get_all_data` now includes an `eyetracking` key.
|
|
12
|
+
- **Room variables**: `get_variables(room_id)` — the reconstructed shared-variable timeline for a room. Returns `{writes, timeline, variable_names, dropped_writes}`: the write log (each write tagged with source and persisted flag), the per-state forward-filled snapshot matrix, the ordered variable set, and any writes that failed the server's ground-truth cross-checks (a bug signal).
|
|
13
|
+
- **Participant counts**: `get_counts(participant_id, room_id=)` — cheap per-data-type document counts (`counts` / `hasData`) for a participant in a room.
|
|
14
|
+
- **Experiment export**: `export_experiment(experiment_id)` — portable experiment JSON (definition plus media-asset manifest), suitable for re-import.
|
|
15
|
+
- **Cognition catalog**: `get_cognition_catalog()` — the valid building blocks for persona `cognition` configs (`abilities`, `recipes`, `offlineRecipes`). Requires the `read:personas` scope.
|
|
16
|
+
|
|
17
|
+
## v0.4.0
|
|
18
|
+
|
|
19
|
+
### Features
|
|
20
|
+
|
|
21
|
+
- **Agent data access**: new methods for AI-agent experiment data —
|
|
22
|
+
`get_agent_decisions(scope_id, scope="experiment"|"room", detail=, limit=, participant_id=)` (per-turn decision logs plus run-manifest rows), `get_agent_decision(room_id, decision_id)` (single decision with full detail blobs: prompt, reasoning chain, peer-model snapshot, prediction update), and `get_agent_runs(experiment_id)` (run manifests with orphaned-run flags). Emits a warning when the server truncates decisions at the per-room limit.
|
|
23
|
+
- `DataType.AGENT_DECISIONS` and `DataType.AGENT_RUNS` enum entries; `get_all_data` now includes an `agent_decisions` key (room-scope fetch filtered to the participant).
|
|
24
|
+
- **Agent-role experiment authoring**: `Role` accepts `mode="agent"` and `persona_id`; new `AgentConfig` (per-role `role_overrides`, `pacing`, `seed`) and `PromptLayer` models; `Experiment` gains `runtime` and `agent_config` fields. Role-name keys in `role_overrides` are preserved (not camelized) on the wire.
|
|
25
|
+
- **Persona management**: `list_personas`, `get_persona`, `create_persona`, `update_persona` (merge-patch), `delete_persona`, `duplicate_persona`, with typed `Persona` / `Guardrails` builders. Requires API-key scopes `read:personas` / `write:personas`.
|
|
26
|
+
- **Agent deployment launch & control**: `create_deployment` (including agent-only deployments — rooms launch server-side immediately), `get_agent_spend`, `run_more`, `stop_room`, `retry_room`. Requires the `write:deployments` scope.
|
|
27
|
+
|
|
28
|
+
### Fixes
|
|
29
|
+
|
|
30
|
+
- `__version__` now matches the packaged version (was stuck at 0.3.0 while pyproject said 0.3.1).
|
|
31
|
+
|
|
3
32
|
## v0.3.1
|
|
4
33
|
|
|
5
34
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hyperstudy
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Python SDK for the HyperStudy experiment platform API
|
|
5
5
|
Project-URL: Homepage, https://hyperstudy.io
|
|
6
6
|
Project-URL: Documentation, https://docs.hyperstudy.io/developers/python-sdk
|
|
@@ -78,6 +78,7 @@ chat = hs.get_chat("exp_id")
|
|
|
78
78
|
videochat = hs.get_videochat("exp_id")
|
|
79
79
|
sync = hs.get_sync("exp_id")
|
|
80
80
|
ratings = hs.get_ratings("exp_id", kind="continuous")
|
|
81
|
+
eyetracking = hs.get_eyetracking("exp_id")
|
|
81
82
|
components = hs.get_components("exp_id")
|
|
82
83
|
participants = hs.get_participants("exp_id")
|
|
83
84
|
rooms = hs.get_rooms("exp_id")
|
|
@@ -86,6 +87,13 @@ rooms = hs.get_rooms("exp_id")
|
|
|
86
87
|
questionnaire = hs.get_questionnaire("exp_id")
|
|
87
88
|
instructions = hs.get_instructions("exp_id")
|
|
88
89
|
consent = hs.get_consent("exp_id")
|
|
90
|
+
|
|
91
|
+
# Room-scoped: reconstructed shared-variable timeline
|
|
92
|
+
# -> {"writes", "timeline", "variable_names", "dropped_writes"}
|
|
93
|
+
variables = hs.get_variables("room_id")
|
|
94
|
+
|
|
95
|
+
# Per-data-type document counts for one participant in a room
|
|
96
|
+
counts = hs.get_counts("participant_id", room_id="room_id")
|
|
89
97
|
```
|
|
90
98
|
|
|
91
99
|
### Output Formats
|
|
@@ -130,6 +138,9 @@ exp = hs.get_experiment("exp_id")
|
|
|
130
138
|
new_exp = hs.create_experiment(name="My Study", description="...")
|
|
131
139
|
hs.update_experiment("exp_id", name="Updated Name")
|
|
132
140
|
hs.delete_experiment("exp_id")
|
|
141
|
+
|
|
142
|
+
# Portable experiment JSON (definition + media-asset manifest)
|
|
143
|
+
export = hs.export_experiment("exp_id")
|
|
133
144
|
```
|
|
134
145
|
|
|
135
146
|
### Building experiments programmatically (0.3.0+)
|
|
@@ -175,13 +186,60 @@ dep = hs.get_deployment("deployment_id")
|
|
|
175
186
|
sessions = hs.get_deployment_sessions("deployment_id")
|
|
176
187
|
```
|
|
177
188
|
|
|
189
|
+
## AI Agents
|
|
190
|
+
|
|
191
|
+
Full agent workflow support: personas (the agent library), agent-role experiment authoring, agent-only deployment launch/control, and agent data.
|
|
192
|
+
|
|
193
|
+
```python
|
|
194
|
+
from hyperstudy import Persona, PromptLayer, Guardrails, Role, AgentConfig
|
|
195
|
+
|
|
196
|
+
# --- Personas (agent library) ---
|
|
197
|
+
persona = hs.create_persona(persona=Persona(
|
|
198
|
+
name="Curious Undergrad",
|
|
199
|
+
provider="anthropic",
|
|
200
|
+
model="claude-opus-4-8",
|
|
201
|
+
prompt=PromptLayer(persona="You are a curious undergraduate...",
|
|
202
|
+
objective="Converse naturally with your partner."),
|
|
203
|
+
guardrails=Guardrails(max_turns=50, budget_usd=2.0),
|
|
204
|
+
))
|
|
205
|
+
personas = hs.list_personas()
|
|
206
|
+
hs.update_persona(persona["id"], description="Updated") # merge-patch
|
|
207
|
+
|
|
208
|
+
# --- Agent roles in experiments ---
|
|
209
|
+
exp = hs.create_experiment(experiment=Experiment(
|
|
210
|
+
name="Agent study",
|
|
211
|
+
runtime="v2", # required for agent-only deployments
|
|
212
|
+
roles={"partner": Role(mode="agent", persona_id=persona["id"])},
|
|
213
|
+
agent_config=AgentConfig(seed=42),
|
|
214
|
+
states=[...],
|
|
215
|
+
))
|
|
216
|
+
|
|
217
|
+
# --- Agent-only deployment ---
|
|
218
|
+
dep = hs.create_deployment(exp["id"], config={
|
|
219
|
+
"type": "agent-only",
|
|
220
|
+
"agentDeployment": {"rooms": 10, "budgetUsd": 5.0},
|
|
221
|
+
})
|
|
222
|
+
spend = hs.get_agent_spend(dep["id"]) # live spend vs budget
|
|
223
|
+
hs.run_more(dep["id"], rooms=5, budget_usd=2.5) # additive batch
|
|
224
|
+
|
|
225
|
+
# --- Agent data ---
|
|
226
|
+
decisions = hs.get_agent_decisions(exp["id"]) # per-turn logs
|
|
227
|
+
runs = hs.get_agent_runs(exp["id"]) # run manifests
|
|
228
|
+
detail = hs.get_agent_decision("room_id", "participantId_3") # full blobs
|
|
229
|
+
|
|
230
|
+
# --- Cognition authoring catalog ---
|
|
231
|
+
catalog = hs.get_cognition_catalog() # {"abilities": ..., "recipes": ..., "offlineRecipes": ...}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Persona methods need the `read:personas` / `write:personas` API-key scopes; deployment launch/control needs `write:deployments`; agent data reads need `read:events`.
|
|
235
|
+
|
|
178
236
|
## All Data for a Participant
|
|
179
237
|
|
|
180
238
|
```python
|
|
181
239
|
data = hs.get_all_data("participant_id", room_id="room_id")
|
|
182
240
|
# Returns dict with keys: events, recordings, chat, videochat, sync,
|
|
183
|
-
# ratings_continuous, ratings_sparse, components,
|
|
184
|
-
# instructions, consent
|
|
241
|
+
# ratings_continuous, ratings_sparse, components, eyetracking,
|
|
242
|
+
# questionnaire, instructions, consent, agent_decisions
|
|
185
243
|
```
|
|
186
244
|
|
|
187
245
|
## API Key
|
|
@@ -43,6 +43,7 @@ chat = hs.get_chat("exp_id")
|
|
|
43
43
|
videochat = hs.get_videochat("exp_id")
|
|
44
44
|
sync = hs.get_sync("exp_id")
|
|
45
45
|
ratings = hs.get_ratings("exp_id", kind="continuous")
|
|
46
|
+
eyetracking = hs.get_eyetracking("exp_id")
|
|
46
47
|
components = hs.get_components("exp_id")
|
|
47
48
|
participants = hs.get_participants("exp_id")
|
|
48
49
|
rooms = hs.get_rooms("exp_id")
|
|
@@ -51,6 +52,13 @@ rooms = hs.get_rooms("exp_id")
|
|
|
51
52
|
questionnaire = hs.get_questionnaire("exp_id")
|
|
52
53
|
instructions = hs.get_instructions("exp_id")
|
|
53
54
|
consent = hs.get_consent("exp_id")
|
|
55
|
+
|
|
56
|
+
# Room-scoped: reconstructed shared-variable timeline
|
|
57
|
+
# -> {"writes", "timeline", "variable_names", "dropped_writes"}
|
|
58
|
+
variables = hs.get_variables("room_id")
|
|
59
|
+
|
|
60
|
+
# Per-data-type document counts for one participant in a room
|
|
61
|
+
counts = hs.get_counts("participant_id", room_id="room_id")
|
|
54
62
|
```
|
|
55
63
|
|
|
56
64
|
### Output Formats
|
|
@@ -95,6 +103,9 @@ exp = hs.get_experiment("exp_id")
|
|
|
95
103
|
new_exp = hs.create_experiment(name="My Study", description="...")
|
|
96
104
|
hs.update_experiment("exp_id", name="Updated Name")
|
|
97
105
|
hs.delete_experiment("exp_id")
|
|
106
|
+
|
|
107
|
+
# Portable experiment JSON (definition + media-asset manifest)
|
|
108
|
+
export = hs.export_experiment("exp_id")
|
|
98
109
|
```
|
|
99
110
|
|
|
100
111
|
### Building experiments programmatically (0.3.0+)
|
|
@@ -140,13 +151,60 @@ dep = hs.get_deployment("deployment_id")
|
|
|
140
151
|
sessions = hs.get_deployment_sessions("deployment_id")
|
|
141
152
|
```
|
|
142
153
|
|
|
154
|
+
## AI Agents
|
|
155
|
+
|
|
156
|
+
Full agent workflow support: personas (the agent library), agent-role experiment authoring, agent-only deployment launch/control, and agent data.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
from hyperstudy import Persona, PromptLayer, Guardrails, Role, AgentConfig
|
|
160
|
+
|
|
161
|
+
# --- Personas (agent library) ---
|
|
162
|
+
persona = hs.create_persona(persona=Persona(
|
|
163
|
+
name="Curious Undergrad",
|
|
164
|
+
provider="anthropic",
|
|
165
|
+
model="claude-opus-4-8",
|
|
166
|
+
prompt=PromptLayer(persona="You are a curious undergraduate...",
|
|
167
|
+
objective="Converse naturally with your partner."),
|
|
168
|
+
guardrails=Guardrails(max_turns=50, budget_usd=2.0),
|
|
169
|
+
))
|
|
170
|
+
personas = hs.list_personas()
|
|
171
|
+
hs.update_persona(persona["id"], description="Updated") # merge-patch
|
|
172
|
+
|
|
173
|
+
# --- Agent roles in experiments ---
|
|
174
|
+
exp = hs.create_experiment(experiment=Experiment(
|
|
175
|
+
name="Agent study",
|
|
176
|
+
runtime="v2", # required for agent-only deployments
|
|
177
|
+
roles={"partner": Role(mode="agent", persona_id=persona["id"])},
|
|
178
|
+
agent_config=AgentConfig(seed=42),
|
|
179
|
+
states=[...],
|
|
180
|
+
))
|
|
181
|
+
|
|
182
|
+
# --- Agent-only deployment ---
|
|
183
|
+
dep = hs.create_deployment(exp["id"], config={
|
|
184
|
+
"type": "agent-only",
|
|
185
|
+
"agentDeployment": {"rooms": 10, "budgetUsd": 5.0},
|
|
186
|
+
})
|
|
187
|
+
spend = hs.get_agent_spend(dep["id"]) # live spend vs budget
|
|
188
|
+
hs.run_more(dep["id"], rooms=5, budget_usd=2.5) # additive batch
|
|
189
|
+
|
|
190
|
+
# --- Agent data ---
|
|
191
|
+
decisions = hs.get_agent_decisions(exp["id"]) # per-turn logs
|
|
192
|
+
runs = hs.get_agent_runs(exp["id"]) # run manifests
|
|
193
|
+
detail = hs.get_agent_decision("room_id", "participantId_3") # full blobs
|
|
194
|
+
|
|
195
|
+
# --- Cognition authoring catalog ---
|
|
196
|
+
catalog = hs.get_cognition_catalog() # {"abilities": ..., "recipes": ..., "offlineRecipes": ...}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Persona methods need the `read:personas` / `write:personas` API-key scopes; deployment launch/control needs `write:deployments`; agent data reads need `read:events`.
|
|
200
|
+
|
|
143
201
|
## All Data for a Participant
|
|
144
202
|
|
|
145
203
|
```python
|
|
146
204
|
data = hs.get_all_data("participant_id", room_id="room_id")
|
|
147
205
|
# Returns dict with keys: events, recordings, chat, videochat, sync,
|
|
148
|
-
# ratings_continuous, ratings_sparse, components,
|
|
149
|
-
# instructions, consent
|
|
206
|
+
# ratings_continuous, ratings_sparse, components, eyetracking,
|
|
207
|
+
# questionnaire, instructions, consent, agent_decisions
|
|
150
208
|
```
|
|
151
209
|
|
|
152
210
|
## API Key
|
|
@@ -30,13 +30,17 @@ from .exceptions import (
|
|
|
30
30
|
ValidationError,
|
|
31
31
|
)
|
|
32
32
|
from .models import (
|
|
33
|
+
AgentConfig,
|
|
33
34
|
ComponentType,
|
|
34
35
|
DisconnectTimeout,
|
|
35
36
|
Experiment,
|
|
36
37
|
FocusComponent,
|
|
37
38
|
GlobalComponentType,
|
|
39
|
+
Guardrails,
|
|
38
40
|
InstructionsPage,
|
|
41
|
+
Persona,
|
|
39
42
|
PostExperimentQuestionnaire,
|
|
43
|
+
PromptLayer,
|
|
40
44
|
Role,
|
|
41
45
|
State,
|
|
42
46
|
TransitionRules,
|
|
@@ -52,7 +56,7 @@ from .models import (
|
|
|
52
56
|
waiting,
|
|
53
57
|
)
|
|
54
58
|
|
|
55
|
-
__version__ = "0.
|
|
59
|
+
__version__ = "0.5.0"
|
|
56
60
|
|
|
57
61
|
__all__ = [
|
|
58
62
|
"HyperStudy",
|
|
@@ -75,6 +79,10 @@ __all__ = [
|
|
|
75
79
|
"State",
|
|
76
80
|
"FocusComponent",
|
|
77
81
|
"Role",
|
|
82
|
+
"AgentConfig",
|
|
83
|
+
"PromptLayer",
|
|
84
|
+
"Persona",
|
|
85
|
+
"Guardrails",
|
|
78
86
|
"TransitionRules",
|
|
79
87
|
"WaitingRoomConfig",
|
|
80
88
|
"DisconnectTimeout",
|
|
@@ -20,9 +20,12 @@ class DataType(str, Enum):
|
|
|
20
20
|
VIDEOCHAT = "videochat"
|
|
21
21
|
SYNC = "sync"
|
|
22
22
|
RATINGS = "ratings"
|
|
23
|
+
EYETRACKING = "eyetracking"
|
|
23
24
|
COMPONENTS = "components"
|
|
24
25
|
PARTICIPANTS = "participants"
|
|
25
26
|
ROOMS = "rooms"
|
|
27
|
+
AGENT_DECISIONS = "agentDecisions"
|
|
28
|
+
AGENT_RUNS = "agentRuns"
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
class RatingKind(str, Enum):
|
|
@@ -15,9 +15,10 @@ from ._pagination import fetch_all_pages
|
|
|
15
15
|
from ._types import Scope
|
|
16
16
|
from .exceptions import HyperStudyError
|
|
17
17
|
from .experiments import ExperimentMixin
|
|
18
|
+
from .personas import PersonaMixin
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
class HyperStudy(ExperimentMixin):
|
|
21
|
+
class HyperStudy(ExperimentMixin, PersonaMixin):
|
|
21
22
|
"""Client for the HyperStudy API v3.
|
|
22
23
|
|
|
23
24
|
Args:
|
|
@@ -234,6 +235,35 @@ class HyperStudy(ExperimentMixin):
|
|
|
234
235
|
output=output, progress=progress,
|
|
235
236
|
)
|
|
236
237
|
|
|
238
|
+
def get_eyetracking(
|
|
239
|
+
self,
|
|
240
|
+
scope_id: str,
|
|
241
|
+
*,
|
|
242
|
+
scope: str = "experiment",
|
|
243
|
+
room_id: str | None = None,
|
|
244
|
+
limit: int | None = None,
|
|
245
|
+
offset: int = 0,
|
|
246
|
+
output: str = "pandas",
|
|
247
|
+
progress: bool = True,
|
|
248
|
+
):
|
|
249
|
+
"""Fetch eye-tracking gaze data.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
scope_id: ID of the experiment, room, or participant.
|
|
253
|
+
scope: ``"experiment"``, ``"room"``, or ``"participant"``.
|
|
254
|
+
room_id: Required when ``scope="participant"``.
|
|
255
|
+
limit: Max records. ``None`` fetches all pages.
|
|
256
|
+
offset: Starting offset.
|
|
257
|
+
output: ``"pandas"`` (default), ``"polars"``, or ``"dict"``.
|
|
258
|
+
progress: Show progress bar when paginating.
|
|
259
|
+
"""
|
|
260
|
+
return self._fetch_data(
|
|
261
|
+
"eyetracking", scope_id,
|
|
262
|
+
scope=scope, room_id=room_id,
|
|
263
|
+
limit=limit, offset=offset,
|
|
264
|
+
output=output, progress=progress,
|
|
265
|
+
)
|
|
266
|
+
|
|
237
267
|
def get_components(
|
|
238
268
|
self,
|
|
239
269
|
scope_id: str,
|
|
@@ -293,6 +323,67 @@ class HyperStudy(ExperimentMixin):
|
|
|
293
323
|
output=output, progress=progress,
|
|
294
324
|
)
|
|
295
325
|
|
|
326
|
+
def get_variables(
|
|
327
|
+
self,
|
|
328
|
+
room_id: str,
|
|
329
|
+
*,
|
|
330
|
+
output: str = "pandas",
|
|
331
|
+
):
|
|
332
|
+
"""Fetch the reconstructed shared-variable timeline for a room.
|
|
333
|
+
|
|
334
|
+
Returns a dict with four keys:
|
|
335
|
+
|
|
336
|
+
- ``writes`` — the time-ordered write log (one row per variable
|
|
337
|
+
write: seeded constant, human component response, or agent
|
|
338
|
+
submit-response, tagged with source and persisted flag), converted
|
|
339
|
+
per ``output``
|
|
340
|
+
- ``timeline`` — the derived per-state forward-filled variable
|
|
341
|
+
snapshot matrix, converted per ``output``
|
|
342
|
+
- ``variable_names`` — ordered list of every variable seen
|
|
343
|
+
- ``matrix_columns`` — ordered column set of the timeline matrix
|
|
344
|
+
- ``dropped_writes`` — writes that failed the server's ground-truth
|
|
345
|
+
cross-checks (a bug signal; empty is the healthy state)
|
|
346
|
+
- ``mode`` — the reconstruction mode the server processor ran in
|
|
347
|
+
|
|
348
|
+
Args:
|
|
349
|
+
room_id: Room ID.
|
|
350
|
+
output: ``"pandas"`` (default), ``"polars"``, or ``"dict"``
|
|
351
|
+
for the tabular values.
|
|
352
|
+
"""
|
|
353
|
+
body = self._transport.get(f"data/variables/room/{room_id}")
|
|
354
|
+
metadata = body.get("metadata") or {}
|
|
355
|
+
return {
|
|
356
|
+
"writes": self._convert_output(body.get("data", []), output),
|
|
357
|
+
"timeline": self._convert_output(metadata.get("timeline") or [], output),
|
|
358
|
+
"variable_names": metadata.get("variableNames") or [],
|
|
359
|
+
"matrix_columns": metadata.get("matrixColumns") or [],
|
|
360
|
+
"dropped_writes": metadata.get("droppedWrites") or [],
|
|
361
|
+
"mode": metadata.get("mode"),
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
def get_counts(
|
|
365
|
+
self,
|
|
366
|
+
participant_id: str,
|
|
367
|
+
room_id: str,
|
|
368
|
+
) -> dict[str, Any]:
|
|
369
|
+
"""Fetch per-data-type document counts for a participant in a room.
|
|
370
|
+
|
|
371
|
+
Cheap count queries grouped by data type — useful for checking
|
|
372
|
+
which data types exist before fetching them.
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
participant_id: Participant ID.
|
|
376
|
+
room_id: Room ID.
|
|
377
|
+
|
|
378
|
+
Returns:
|
|
379
|
+
Dict with ``counts`` and ``hasData`` keyed by data type.
|
|
380
|
+
"""
|
|
381
|
+
body = self._transport.get(
|
|
382
|
+
f"data/counts/participant/{participant_id}", params={"roomId": room_id}
|
|
383
|
+
)
|
|
384
|
+
data = body.get("data", [])
|
|
385
|
+
return data[0] if isinstance(data, list) and data else data
|
|
386
|
+
|
|
296
387
|
# ------------------------------------------------------------------
|
|
297
388
|
# Convenience: category-filtered events
|
|
298
389
|
# ------------------------------------------------------------------
|
|
@@ -441,6 +532,177 @@ class HyperStudy(ExperimentMixin):
|
|
|
441
532
|
data = body.get("data", [])
|
|
442
533
|
return self._convert_output(data, output)
|
|
443
534
|
|
|
535
|
+
# ------------------------------------------------------------------
|
|
536
|
+
# Deployments — agent-deployment write surface
|
|
537
|
+
# ------------------------------------------------------------------
|
|
538
|
+
|
|
539
|
+
def create_deployment(
|
|
540
|
+
self,
|
|
541
|
+
experiment_id: str,
|
|
542
|
+
*,
|
|
543
|
+
config: dict[str, Any] | None = None,
|
|
544
|
+
**kwargs: Any,
|
|
545
|
+
) -> dict[str, Any]:
|
|
546
|
+
"""Create a deployment (requires the ``write:deployments`` scope).
|
|
547
|
+
|
|
548
|
+
For an agent-only deployment, pass::
|
|
549
|
+
|
|
550
|
+
hs.create_deployment(
|
|
551
|
+
"exp_123",
|
|
552
|
+
config={
|
|
553
|
+
"name": "Pilot batch",
|
|
554
|
+
"type": "agent-only",
|
|
555
|
+
"agentDeployment": {"rooms": 10, "budgetUsd": 5.0},
|
|
556
|
+
},
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
Agent rooms launch server-side immediately after creation. Preflight
|
|
560
|
+
failures (missing persona binding, missing provider key, unreachable
|
|
561
|
+
custom endpoint) raise :class:`ValidationError` with per-role reasons.
|
|
562
|
+
|
|
563
|
+
Args:
|
|
564
|
+
experiment_id: The experiment to deploy (agent-only requires
|
|
565
|
+
``runtime="v2"`` and at least one agent-mode role).
|
|
566
|
+
config: Deployment config dict (camelCase keys, as above).
|
|
567
|
+
**kwargs: Extra config fields, merged into ``config``.
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
The created deployment dict.
|
|
571
|
+
"""
|
|
572
|
+
merged = {**(config or {}), **kwargs}
|
|
573
|
+
body = self._transport.post(
|
|
574
|
+
"deployments", json={"experimentId": experiment_id, "config": merged}
|
|
575
|
+
)
|
|
576
|
+
return body.get("data", {})
|
|
577
|
+
|
|
578
|
+
def get_agent_spend(self, deployment_id: str) -> dict[str, Any]:
|
|
579
|
+
"""Total + per-room agent LLM spend for an agent-only deployment."""
|
|
580
|
+
body = self._transport.get(f"deployments/{deployment_id}/agent-spend")
|
|
581
|
+
return body.get("data", {})
|
|
582
|
+
|
|
583
|
+
def run_more(
|
|
584
|
+
self, deployment_id: str, *, rooms: int, budget_usd: float
|
|
585
|
+
) -> dict[str, Any]:
|
|
586
|
+
"""Launch additional agent rooms on an existing agent-only deployment.
|
|
587
|
+
|
|
588
|
+
Args:
|
|
589
|
+
deployment_id: The deployment to extend.
|
|
590
|
+
rooms: Number of additional rooms.
|
|
591
|
+
budget_usd: Additional budget for this batch (added to the
|
|
592
|
+
deployment's cumulative budget cap).
|
|
593
|
+
|
|
594
|
+
Returns:
|
|
595
|
+
Dict with the launched ``batchId`` and ``requestedRooms``.
|
|
596
|
+
"""
|
|
597
|
+
body = self._transport.post(
|
|
598
|
+
f"deployments/{deployment_id}/run-more",
|
|
599
|
+
json={"rooms": rooms, "budgetUsd": budget_usd},
|
|
600
|
+
)
|
|
601
|
+
return body.get("data", {})
|
|
602
|
+
|
|
603
|
+
def stop_room(self, deployment_id: str, room_id: str) -> None:
|
|
604
|
+
"""Force-end a running agent room."""
|
|
605
|
+
self._transport.post(f"deployments/{deployment_id}/rooms/{room_id}/stop")
|
|
606
|
+
|
|
607
|
+
def retry_room(self, deployment_id: str, room_id: str) -> dict[str, Any]:
|
|
608
|
+
"""Re-spawn a fresh room for a spawn-failed one (reuses the budget pool)."""
|
|
609
|
+
body = self._transport.post(
|
|
610
|
+
f"deployments/{deployment_id}/rooms/{room_id}/retry"
|
|
611
|
+
)
|
|
612
|
+
return body.get("data", {})
|
|
613
|
+
|
|
614
|
+
# ------------------------------------------------------------------
|
|
615
|
+
# Agent data — decisions and run manifests
|
|
616
|
+
# ------------------------------------------------------------------
|
|
617
|
+
|
|
618
|
+
def get_agent_decisions(
|
|
619
|
+
self,
|
|
620
|
+
scope_id: str,
|
|
621
|
+
*,
|
|
622
|
+
scope: str = "experiment",
|
|
623
|
+
detail: bool = False,
|
|
624
|
+
limit: int | None = None,
|
|
625
|
+
participant_id: str | None = None,
|
|
626
|
+
output: str = "pandas",
|
|
627
|
+
):
|
|
628
|
+
"""Fetch AI-agent decision logs (and run manifests).
|
|
629
|
+
|
|
630
|
+
Every agent turn is logged as a decision record; each agent's run
|
|
631
|
+
also produces one manifest row. Room-scope responses tag rows with
|
|
632
|
+
``_type`` (``"decision"`` or ``"run"``).
|
|
633
|
+
|
|
634
|
+
Args:
|
|
635
|
+
scope_id: Experiment ID or room ID, depending on ``scope``.
|
|
636
|
+
scope: ``"experiment"`` (default, all rooms) or ``"room"``.
|
|
637
|
+
detail: Include full detail blobs (prompt, reasoning chain,
|
|
638
|
+
peer-model snapshot, prediction update) on each decision row.
|
|
639
|
+
limit: Max decisions per room (server default 5000). When the
|
|
640
|
+
cap is hit a truncation warning is emitted.
|
|
641
|
+
participant_id: Optionally filter rows to one agent participant
|
|
642
|
+
(applied client-side).
|
|
643
|
+
output: ``"pandas"`` (default), ``"polars"``, or ``"dict"``.
|
|
644
|
+
"""
|
|
645
|
+
if scope not in ("experiment", "room"):
|
|
646
|
+
raise ValueError(f"scope must be 'experiment' or 'room', got {scope!r}")
|
|
647
|
+
|
|
648
|
+
params: dict[str, Any] = {}
|
|
649
|
+
if detail:
|
|
650
|
+
params["detail"] = "true"
|
|
651
|
+
if limit is not None:
|
|
652
|
+
params["limit"] = limit
|
|
653
|
+
|
|
654
|
+
body = self._transport.get(
|
|
655
|
+
f"data/agent-decisions/{scope}/{scope_id}", params=params or None
|
|
656
|
+
)
|
|
657
|
+
data = body.get("data", [])
|
|
658
|
+
if (body.get("metadata") or {}).get("truncated"):
|
|
659
|
+
warnings.warn(
|
|
660
|
+
"Agent decisions were truncated by the per-room limit; "
|
|
661
|
+
"pass a higher limit= to fetch more.",
|
|
662
|
+
stacklevel=2,
|
|
663
|
+
)
|
|
664
|
+
if participant_id is not None:
|
|
665
|
+
data = [row for row in data if row.get("participantId") == participant_id]
|
|
666
|
+
return self._convert_output(data, output)
|
|
667
|
+
|
|
668
|
+
def get_agent_decision(
|
|
669
|
+
self, room_id: str, decision_id: str
|
|
670
|
+
) -> dict[str, Any]:
|
|
671
|
+
"""Fetch one agent decision with full detail blobs.
|
|
672
|
+
|
|
673
|
+
Args:
|
|
674
|
+
room_id: Room the decision belongs to.
|
|
675
|
+
decision_id: Decision document ID (``{participantId}_{seq}``).
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
Decision dict including prompt, reasoning chain, peer-model
|
|
679
|
+
snapshot, and prediction update.
|
|
680
|
+
"""
|
|
681
|
+
body = self._transport.get(
|
|
682
|
+
f"data/agent-decisions/room/{room_id}/decision/{decision_id}"
|
|
683
|
+
)
|
|
684
|
+
return body.get("data", {})
|
|
685
|
+
|
|
686
|
+
def get_agent_runs(
|
|
687
|
+
self,
|
|
688
|
+
experiment_id: str,
|
|
689
|
+
*,
|
|
690
|
+
output: str = "pandas",
|
|
691
|
+
):
|
|
692
|
+
"""Fetch agent run manifests for an experiment.
|
|
693
|
+
|
|
694
|
+
One row per agent run: model, provider, token/cost totals, seed,
|
|
695
|
+
persona ID, and code version. Runs whose room ended abnormally are
|
|
696
|
+
flagged as orphaned.
|
|
697
|
+
|
|
698
|
+
Args:
|
|
699
|
+
experiment_id: Experiment ID.
|
|
700
|
+
output: ``"pandas"`` (default), ``"polars"``, or ``"dict"``.
|
|
701
|
+
"""
|
|
702
|
+
body = self._transport.get(f"data/agent-runs/experiment/{experiment_id}")
|
|
703
|
+
data = body.get("data", [])
|
|
704
|
+
return self._convert_output(data, output)
|
|
705
|
+
|
|
444
706
|
# ------------------------------------------------------------------
|
|
445
707
|
# Convenience: all data for a participant
|
|
446
708
|
# ------------------------------------------------------------------
|
|
@@ -467,9 +729,13 @@ class HyperStudy(ExperimentMixin):
|
|
|
467
729
|
"ratings_continuous": self.get_ratings(participant_id, kind="continuous", **common),
|
|
468
730
|
"ratings_sparse": self.get_ratings(participant_id, kind="sparse", **common),
|
|
469
731
|
"components": self.get_components(participant_id, **common),
|
|
732
|
+
"eyetracking": self.get_eyetracking(participant_id, **common),
|
|
470
733
|
"questionnaire": self.get_questionnaire(participant_id, **common),
|
|
471
734
|
"instructions": self.get_instructions(participant_id, **common),
|
|
472
735
|
"consent": self.get_consent(participant_id, **common),
|
|
736
|
+
"agent_decisions": self.get_agent_decisions(
|
|
737
|
+
room_id, scope="room", participant_id=participant_id, output=output
|
|
738
|
+
),
|
|
473
739
|
}
|
|
474
740
|
|
|
475
741
|
# ------------------------------------------------------------------
|
|
@@ -115,6 +115,17 @@ class ExperimentMixin:
|
|
|
115
115
|
data = body.get("data", [])
|
|
116
116
|
return data[0] if isinstance(data, list) and data else data
|
|
117
117
|
|
|
118
|
+
def export_experiment(self, experiment_id: str) -> dict[str, Any]:
|
|
119
|
+
"""Export an experiment as portable JSON.
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
The export payload (experiment definition plus media-asset
|
|
123
|
+
manifest), suitable for re-import.
|
|
124
|
+
"""
|
|
125
|
+
body = self._transport.get(f"experiments/{experiment_id}/export")
|
|
126
|
+
data = body.get("data", [])
|
|
127
|
+
return data[0] if isinstance(data, list) and data else data
|
|
128
|
+
|
|
118
129
|
# ------------------------------------------------------------------
|
|
119
130
|
# Write
|
|
120
131
|
# ------------------------------------------------------------------
|