hyperstudy 0.4.0__tar.gz → 0.4.2__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.4.0 → hyperstudy-0.4.2}/.github/workflows/publish.yml +2 -1
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/.github/workflows/test.yml +2 -1
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/CHANGELOG.md +41 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/PKG-INFO +68 -4
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/README.md +66 -2
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/pyproject.toml +1 -1
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/__init__.py +15 -1
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_http.py +17 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_types.py +2 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/client.py +223 -12
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/experiments.py +22 -15
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/models.py +176 -23
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/personas.py +45 -6
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/conftest.py +14 -0
- hyperstudy-0.4.2/tests/fixtures/agent_decision_detail_response.json +44 -0
- hyperstudy-0.4.2/tests/fixtures/deployment_single_response.json +22 -0
- hyperstudy-0.4.2/tests/test_agent_authoring.py +190 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_client.py +124 -3
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_deployments_write.py +6 -10
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_experiments.py +32 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_http.py +20 -0
- hyperstudy-0.4.2/tests/test_personas.py +193 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_recordings_download.py +4 -2
- hyperstudy-0.4.2/tests/test_voice_and_filters.py +139 -0
- hyperstudy-0.4.2/tests/test_wire_keys.py +195 -0
- hyperstudy-0.4.0/tests/fixtures/agent_decision_detail_response.json +0 -30
- hyperstudy-0.4.0/tests/fixtures/deployment_single_response.json +0 -20
- hyperstudy-0.4.0/tests/test_personas.py +0 -127
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/.github/workflows/sync-release-notes.yml +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/.gitignore +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/LICENSE +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/docs/superpowers/specs/2026-04-10-recording-downloads-design.md +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_dataframe.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_display.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_downloads.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/_pagination.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/src/hyperstudy/exceptions.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/__init__.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/agent_decisions_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/agent_decisions_truncated_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/agent_runs_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/deployment_sessions_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/deployments_list_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/error_401.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/error_403.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/events_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/experiment.schema.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/experiment_single_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/experiments_list_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/paginated_page1.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/paginated_page2.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/pre_experiment_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/recordings_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/sparse_ratings_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/fixtures/warnings_response.json +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_agent_data.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_dataframe.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_downloads.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_models.py +0 -0
- {hyperstudy-0.4.0 → hyperstudy-0.4.2}/tests/test_pagination.py +0 -0
|
@@ -1,5 +1,46 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v0.4.2
|
|
4
|
+
|
|
5
|
+
### Breaking Changes
|
|
6
|
+
|
|
7
|
+
- **Removed `Persona.language`.** The server discarded it and nothing read it. The SDK strips it from persona payloads, so personas rebuilt from older stored documents still save, and warns when it drops a value someone set. To set the language participants speak, use the experiment's `agent_config.voice_transcription.language`.
|
|
8
|
+
|
|
9
|
+
### Fixes
|
|
10
|
+
|
|
11
|
+
- **Single-object responses are now returned as objects.** The v3 API always sends `data` as an array, so `get_persona`, `create_persona`, `update_persona`, `duplicate_persona`, `create_deployment`, `get_agent_spend`, `run_more`, `retry_room` and `get_agent_decision` returned a one-element list instead of a dict, and `download_recording` / `download_recordings` raised `AttributeError` for recordings with a `downloadPath`. A single-object response with no record now raises `HyperStudyError` instead of returning `{}`. Test mocks now build the envelope exactly as the server does.
|
|
12
|
+
- **Keys that are not the SDK's own are sent as written** on the kwargs and raw-dict paths. Previously `extra_params={"top_k": 20}` reached a custom provider as `topK`, a cognition context named `post_trial` was stored as `postTrial`, voice turn-detection settings (`silence_duration_ms`, ...) were camelCased and rejected by the server, `prolific_enabled` / `prolific_config` were renamed and dropped, and stimulus mapping names were renamed. Schema fields such as `waiting_room_config` are still converted.
|
|
13
|
+
- **Typed models can be passed as keyword arguments**: `update_experiment(agent_config=AgentConfig(...))`, `update_experiment(waiting_room_config=WaitingRoomConfig(...))` and `update_persona(voice=PersonaVoice(...))` raised `TypeError: Object of type ... is not JSON serializable`.
|
|
14
|
+
- `Persona(display_name=...)` and other undeclared persona fields were sent in snake_case and silently ignored by the server; `display_name` is now a declared field.
|
|
15
|
+
|
|
16
|
+
### Features
|
|
17
|
+
|
|
18
|
+
- **Voice agent conversations**: `get_voice(scope_id, scope=, deployment_id=, room_id=, include_context=, order=)` returns one row per voice event: every speaker's utterances with transcripts, discarded and silent agent replies, what the voice engine was told, and engine connection drops. `DataType.VOICE`; `get_all_data` includes `voice`. Requires the `read:events` scope.
|
|
19
|
+
- **Voice personas**: `Persona.voice` (`PersonaVoice`, `RealtimeVoice`: engine, preset, speed, style, realtime provider and model), `Persona.display_name`, and `Persona.chat` (`PersonaChat`: `initiate`, `initiate_delay_ms`).
|
|
20
|
+
- **Voice settings per experiment**: `AgentConfig.voice_turn_taking` (`VoiceTurnTaking` with `EngineTurnDetection`, sent in the engine's own snake_case) and `AgentConfig.voice_transcription` (`VoiceTranscription`). Ranges mirror the server's validators, so mistakes fail when the model is built.
|
|
21
|
+
- **Experiment fields**: `visibility`, `tags`, `enable_participant_messaging`, `experimenter_message_style` (`ExperimenterMessageStyle`), `stimulus_mappings`, `device_integration`, `prolific_enabled`, `prolific_config`.
|
|
22
|
+
- **Data filters**: `get_components(component_type=, state_id=, completed=, start_time=, end_time=, sort=, order=)`, `get_participants(completion_code=, sort=, order=)`, `get_rooms(sort=, order=)`.
|
|
23
|
+
- **`check_data_access(experiment_id)`**: `{canView, canExport}` without fetching data.
|
|
24
|
+
|
|
25
|
+
### Notes
|
|
26
|
+
|
|
27
|
+
- `update_persona` and `update_experiment` replace a nested block (`voice`, `chat`, `prompt`, `guardrails`, `agent_config`, ...) whole; pass the complete block to change one setting in it. Only an experiment's or persona's owner, or an organization admin, may change its `visibility`.
|
|
28
|
+
- New server-side columns arrive in the existing DataFrames with no SDK change: event clock fields (`clientTime`, `timestampSource`, `clockOffsetMs`, `rawOnset`, `clockOffsetApplied`), `decisionError` on agent decisions, and `completionReason` / `agentStartFailures` on rooms.
|
|
29
|
+
|
|
30
|
+
## v0.4.1
|
|
31
|
+
|
|
32
|
+
### Breaking Changes
|
|
33
|
+
|
|
34
|
+
- **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.
|
|
35
|
+
|
|
36
|
+
### Features
|
|
37
|
+
|
|
38
|
+
- **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.
|
|
39
|
+
- **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).
|
|
40
|
+
- **Participant counts**: `get_counts(participant_id, room_id=)` — cheap per-data-type document counts (`counts` / `hasData`) for a participant in a room.
|
|
41
|
+
- **Experiment export**: `export_experiment(experiment_id)` — portable experiment JSON (definition plus media-asset manifest), suitable for re-import.
|
|
42
|
+
- **Cognition catalog**: `get_cognition_catalog()` — the valid building blocks for persona `cognition` configs (`abilities`, `recipes`, `offlineRecipes`). Requires the `read:personas` scope.
|
|
43
|
+
|
|
3
44
|
## v0.4.0
|
|
4
45
|
|
|
5
46
|
### Features
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: hyperstudy
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
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
|
|
@@ -75,9 +75,11 @@ All data retrieval methods follow the same pattern:
|
|
|
75
75
|
events = hs.get_events("exp_id")
|
|
76
76
|
recordings = hs.get_recordings("exp_id")
|
|
77
77
|
chat = hs.get_chat("exp_id")
|
|
78
|
+
voice = hs.get_voice("exp_id") # voice agent conversations
|
|
78
79
|
videochat = hs.get_videochat("exp_id")
|
|
79
80
|
sync = hs.get_sync("exp_id")
|
|
80
81
|
ratings = hs.get_ratings("exp_id", kind="continuous")
|
|
82
|
+
eyetracking = hs.get_eyetracking("exp_id")
|
|
81
83
|
components = hs.get_components("exp_id")
|
|
82
84
|
participants = hs.get_participants("exp_id")
|
|
83
85
|
rooms = hs.get_rooms("exp_id")
|
|
@@ -86,6 +88,16 @@ rooms = hs.get_rooms("exp_id")
|
|
|
86
88
|
questionnaire = hs.get_questionnaire("exp_id")
|
|
87
89
|
instructions = hs.get_instructions("exp_id")
|
|
88
90
|
consent = hs.get_consent("exp_id")
|
|
91
|
+
|
|
92
|
+
# Room-scoped: reconstructed shared-variable timeline
|
|
93
|
+
# -> {"writes", "timeline", "variable_names", "dropped_writes"}
|
|
94
|
+
variables = hs.get_variables("room_id")
|
|
95
|
+
|
|
96
|
+
# Per-data-type document counts for one participant in a room
|
|
97
|
+
counts = hs.get_counts("participant_id", room_id="room_id")
|
|
98
|
+
|
|
99
|
+
# Whether you may view / export an experiment's data (no data fetched)
|
|
100
|
+
access = hs.check_data_access("exp_id") # {"canView": True, "canExport": False}
|
|
89
101
|
```
|
|
90
102
|
|
|
91
103
|
### Output Formats
|
|
@@ -107,6 +119,9 @@ events = hs.get_events(
|
|
|
107
119
|
sort="onset",
|
|
108
120
|
limit=100,
|
|
109
121
|
)
|
|
122
|
+
|
|
123
|
+
ratings = hs.get_components("exp_id", component_type="vasrating", completed=True)
|
|
124
|
+
abandoned = hs.get_participants("exp_id", completion_code="ABANDONED")
|
|
110
125
|
```
|
|
111
126
|
|
|
112
127
|
### Auto-Pagination
|
|
@@ -130,6 +145,9 @@ exp = hs.get_experiment("exp_id")
|
|
|
130
145
|
new_exp = hs.create_experiment(name="My Study", description="...")
|
|
131
146
|
hs.update_experiment("exp_id", name="Updated Name")
|
|
132
147
|
hs.delete_experiment("exp_id")
|
|
148
|
+
|
|
149
|
+
# Portable experiment JSON (definition + media-asset manifest)
|
|
150
|
+
export = hs.export_experiment("exp_id")
|
|
133
151
|
```
|
|
134
152
|
|
|
135
153
|
### Building experiments programmatically (0.3.0+)
|
|
@@ -215,8 +233,54 @@ hs.run_more(dep["id"], rooms=5, budget_usd=2.5) # additive batch
|
|
|
215
233
|
decisions = hs.get_agent_decisions(exp["id"]) # per-turn logs
|
|
216
234
|
runs = hs.get_agent_runs(exp["id"]) # run manifests
|
|
217
235
|
detail = hs.get_agent_decision("room_id", "participantId_3") # full blobs
|
|
236
|
+
|
|
237
|
+
# --- Cognition authoring catalog ---
|
|
238
|
+
catalog = hs.get_cognition_catalog() # {"abilities": ..., "recipes": ..., "offlineRecipes": ...}
|
|
218
239
|
```
|
|
219
240
|
|
|
241
|
+
### Voice agents
|
|
242
|
+
|
|
243
|
+
A persona with a `voice` speaks in the experiment's audio. The experiment sets how its voice agents take turns and whether participants' speech is transcribed.
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from hyperstudy import (
|
|
247
|
+
PersonaVoice, RealtimeVoice, VoiceTurnTaking, EngineTurnDetection, VoiceTranscription,
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
voice_persona = hs.create_persona(persona=Persona(
|
|
251
|
+
name="Interviewer",
|
|
252
|
+
display_name="Sam", # what other participants see
|
|
253
|
+
provider="openai",
|
|
254
|
+
voice=PersonaVoice(
|
|
255
|
+
engine="realtime",
|
|
256
|
+
preset="marin", # the provider's voice
|
|
257
|
+
speed=1.0,
|
|
258
|
+
style="warm, a little hesitant",
|
|
259
|
+
realtime=RealtimeVoice(provider="openai", model="gpt-realtime-2.1"),
|
|
260
|
+
),
|
|
261
|
+
))
|
|
262
|
+
|
|
263
|
+
exp = hs.create_experiment(experiment=Experiment(
|
|
264
|
+
name="Voice interview",
|
|
265
|
+
runtime="v2",
|
|
266
|
+
roles={"interviewer": Role(mode="agent", persona_id=voice_persona["id"])},
|
|
267
|
+
agent_config=AgentConfig(
|
|
268
|
+
voice_turn_taking=VoiceTurnTaking(
|
|
269
|
+
engine_turn_detection=EngineTurnDetection(type="server_vad", silence_duration_ms=800),
|
|
270
|
+
min_response_delay_ms=250,
|
|
271
|
+
),
|
|
272
|
+
voice_transcription=VoiceTranscription(enabled=True, language="en"),
|
|
273
|
+
),
|
|
274
|
+
states=[...],
|
|
275
|
+
))
|
|
276
|
+
|
|
277
|
+
# One room's conversation: every speaker's turns, plus what the engine was told
|
|
278
|
+
conversation = hs.get_voice("room_id", scope="room")
|
|
279
|
+
turns = conversation[conversation["kind"] == "utterance"][["onset", "speaker", "transcript"]]
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
`update_persona` and `update_experiment` replace a nested block whole, so pass the complete `voice` or `agent_config` when changing one setting in it.
|
|
283
|
+
|
|
220
284
|
Persona methods need the `read:personas` / `write:personas` API-key scopes; deployment launch/control needs `write:deployments`; agent data reads need `read:events`.
|
|
221
285
|
|
|
222
286
|
## All Data for a Participant
|
|
@@ -224,8 +288,8 @@ Persona methods need the `read:personas` / `write:personas` API-key scopes; depl
|
|
|
224
288
|
```python
|
|
225
289
|
data = hs.get_all_data("participant_id", room_id="room_id")
|
|
226
290
|
# Returns dict with keys: events, recordings, chat, videochat, sync,
|
|
227
|
-
# ratings_continuous, ratings_sparse, components,
|
|
228
|
-
# instructions, consent, agent_decisions
|
|
291
|
+
# ratings_continuous, ratings_sparse, components, eyetracking, voice,
|
|
292
|
+
# questionnaire, instructions, consent, agent_decisions
|
|
229
293
|
```
|
|
230
294
|
|
|
231
295
|
## API Key
|
|
@@ -40,9 +40,11 @@ All data retrieval methods follow the same pattern:
|
|
|
40
40
|
events = hs.get_events("exp_id")
|
|
41
41
|
recordings = hs.get_recordings("exp_id")
|
|
42
42
|
chat = hs.get_chat("exp_id")
|
|
43
|
+
voice = hs.get_voice("exp_id") # voice agent conversations
|
|
43
44
|
videochat = hs.get_videochat("exp_id")
|
|
44
45
|
sync = hs.get_sync("exp_id")
|
|
45
46
|
ratings = hs.get_ratings("exp_id", kind="continuous")
|
|
47
|
+
eyetracking = hs.get_eyetracking("exp_id")
|
|
46
48
|
components = hs.get_components("exp_id")
|
|
47
49
|
participants = hs.get_participants("exp_id")
|
|
48
50
|
rooms = hs.get_rooms("exp_id")
|
|
@@ -51,6 +53,16 @@ rooms = hs.get_rooms("exp_id")
|
|
|
51
53
|
questionnaire = hs.get_questionnaire("exp_id")
|
|
52
54
|
instructions = hs.get_instructions("exp_id")
|
|
53
55
|
consent = hs.get_consent("exp_id")
|
|
56
|
+
|
|
57
|
+
# Room-scoped: reconstructed shared-variable timeline
|
|
58
|
+
# -> {"writes", "timeline", "variable_names", "dropped_writes"}
|
|
59
|
+
variables = hs.get_variables("room_id")
|
|
60
|
+
|
|
61
|
+
# Per-data-type document counts for one participant in a room
|
|
62
|
+
counts = hs.get_counts("participant_id", room_id="room_id")
|
|
63
|
+
|
|
64
|
+
# Whether you may view / export an experiment's data (no data fetched)
|
|
65
|
+
access = hs.check_data_access("exp_id") # {"canView": True, "canExport": False}
|
|
54
66
|
```
|
|
55
67
|
|
|
56
68
|
### Output Formats
|
|
@@ -72,6 +84,9 @@ events = hs.get_events(
|
|
|
72
84
|
sort="onset",
|
|
73
85
|
limit=100,
|
|
74
86
|
)
|
|
87
|
+
|
|
88
|
+
ratings = hs.get_components("exp_id", component_type="vasrating", completed=True)
|
|
89
|
+
abandoned = hs.get_participants("exp_id", completion_code="ABANDONED")
|
|
75
90
|
```
|
|
76
91
|
|
|
77
92
|
### Auto-Pagination
|
|
@@ -95,6 +110,9 @@ exp = hs.get_experiment("exp_id")
|
|
|
95
110
|
new_exp = hs.create_experiment(name="My Study", description="...")
|
|
96
111
|
hs.update_experiment("exp_id", name="Updated Name")
|
|
97
112
|
hs.delete_experiment("exp_id")
|
|
113
|
+
|
|
114
|
+
# Portable experiment JSON (definition + media-asset manifest)
|
|
115
|
+
export = hs.export_experiment("exp_id")
|
|
98
116
|
```
|
|
99
117
|
|
|
100
118
|
### Building experiments programmatically (0.3.0+)
|
|
@@ -180,8 +198,54 @@ hs.run_more(dep["id"], rooms=5, budget_usd=2.5) # additive batch
|
|
|
180
198
|
decisions = hs.get_agent_decisions(exp["id"]) # per-turn logs
|
|
181
199
|
runs = hs.get_agent_runs(exp["id"]) # run manifests
|
|
182
200
|
detail = hs.get_agent_decision("room_id", "participantId_3") # full blobs
|
|
201
|
+
|
|
202
|
+
# --- Cognition authoring catalog ---
|
|
203
|
+
catalog = hs.get_cognition_catalog() # {"abilities": ..., "recipes": ..., "offlineRecipes": ...}
|
|
183
204
|
```
|
|
184
205
|
|
|
206
|
+
### Voice agents
|
|
207
|
+
|
|
208
|
+
A persona with a `voice` speaks in the experiment's audio. The experiment sets how its voice agents take turns and whether participants' speech is transcribed.
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from hyperstudy import (
|
|
212
|
+
PersonaVoice, RealtimeVoice, VoiceTurnTaking, EngineTurnDetection, VoiceTranscription,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
voice_persona = hs.create_persona(persona=Persona(
|
|
216
|
+
name="Interviewer",
|
|
217
|
+
display_name="Sam", # what other participants see
|
|
218
|
+
provider="openai",
|
|
219
|
+
voice=PersonaVoice(
|
|
220
|
+
engine="realtime",
|
|
221
|
+
preset="marin", # the provider's voice
|
|
222
|
+
speed=1.0,
|
|
223
|
+
style="warm, a little hesitant",
|
|
224
|
+
realtime=RealtimeVoice(provider="openai", model="gpt-realtime-2.1"),
|
|
225
|
+
),
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
exp = hs.create_experiment(experiment=Experiment(
|
|
229
|
+
name="Voice interview",
|
|
230
|
+
runtime="v2",
|
|
231
|
+
roles={"interviewer": Role(mode="agent", persona_id=voice_persona["id"])},
|
|
232
|
+
agent_config=AgentConfig(
|
|
233
|
+
voice_turn_taking=VoiceTurnTaking(
|
|
234
|
+
engine_turn_detection=EngineTurnDetection(type="server_vad", silence_duration_ms=800),
|
|
235
|
+
min_response_delay_ms=250,
|
|
236
|
+
),
|
|
237
|
+
voice_transcription=VoiceTranscription(enabled=True, language="en"),
|
|
238
|
+
),
|
|
239
|
+
states=[...],
|
|
240
|
+
))
|
|
241
|
+
|
|
242
|
+
# One room's conversation: every speaker's turns, plus what the engine was told
|
|
243
|
+
conversation = hs.get_voice("room_id", scope="room")
|
|
244
|
+
turns = conversation[conversation["kind"] == "utterance"][["onset", "speaker", "transcript"]]
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
`update_persona` and `update_experiment` replace a nested block whole, so pass the complete `voice` or `agent_config` when changing one setting in it.
|
|
248
|
+
|
|
185
249
|
Persona methods need the `read:personas` / `write:personas` API-key scopes; deployment launch/control needs `write:deployments`; agent data reads need `read:events`.
|
|
186
250
|
|
|
187
251
|
## All Data for a Participant
|
|
@@ -189,8 +253,8 @@ Persona methods need the `read:personas` / `write:personas` API-key scopes; depl
|
|
|
189
253
|
```python
|
|
190
254
|
data = hs.get_all_data("participant_id", room_id="room_id")
|
|
191
255
|
# Returns dict with keys: events, recordings, chat, videochat, sync,
|
|
192
|
-
# ratings_continuous, ratings_sparse, components,
|
|
193
|
-
# instructions, consent, agent_decisions
|
|
256
|
+
# ratings_continuous, ratings_sparse, components, eyetracking, voice,
|
|
257
|
+
# questionnaire, instructions, consent, agent_decisions
|
|
194
258
|
```
|
|
195
259
|
|
|
196
260
|
## API Key
|
|
@@ -33,17 +33,24 @@ from .models import (
|
|
|
33
33
|
AgentConfig,
|
|
34
34
|
ComponentType,
|
|
35
35
|
DisconnectTimeout,
|
|
36
|
+
EngineTurnDetection,
|
|
36
37
|
Experiment,
|
|
38
|
+
ExperimenterMessageStyle,
|
|
37
39
|
FocusComponent,
|
|
38
40
|
GlobalComponentType,
|
|
39
41
|
Guardrails,
|
|
40
42
|
InstructionsPage,
|
|
41
43
|
Persona,
|
|
44
|
+
PersonaChat,
|
|
45
|
+
PersonaVoice,
|
|
42
46
|
PostExperimentQuestionnaire,
|
|
43
47
|
PromptLayer,
|
|
48
|
+
RealtimeVoice,
|
|
44
49
|
Role,
|
|
45
50
|
State,
|
|
46
51
|
TransitionRules,
|
|
52
|
+
VoiceTranscription,
|
|
53
|
+
VoiceTurnTaking,
|
|
47
54
|
WaitingRoomConfig,
|
|
48
55
|
likert_scale,
|
|
49
56
|
multiple_choice,
|
|
@@ -56,7 +63,7 @@ from .models import (
|
|
|
56
63
|
waiting,
|
|
57
64
|
)
|
|
58
65
|
|
|
59
|
-
__version__ = "0.4.
|
|
66
|
+
__version__ = "0.4.2"
|
|
60
67
|
|
|
61
68
|
__all__ = [
|
|
62
69
|
"HyperStudy",
|
|
@@ -82,7 +89,14 @@ __all__ = [
|
|
|
82
89
|
"AgentConfig",
|
|
83
90
|
"PromptLayer",
|
|
84
91
|
"Persona",
|
|
92
|
+
"PersonaVoice",
|
|
93
|
+
"RealtimeVoice",
|
|
94
|
+
"PersonaChat",
|
|
85
95
|
"Guardrails",
|
|
96
|
+
"VoiceTurnTaking",
|
|
97
|
+
"EngineTurnDetection",
|
|
98
|
+
"VoiceTranscription",
|
|
99
|
+
"ExperimenterMessageStyle",
|
|
86
100
|
"TransitionRules",
|
|
87
101
|
"WaitingRoomConfig",
|
|
88
102
|
"DisconnectTimeout",
|
|
@@ -25,6 +25,23 @@ _STATUS_MAP = {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def single_record(body: dict) -> dict:
|
|
29
|
+
"""Return the one record of a single-object response.
|
|
30
|
+
|
|
31
|
+
The v3 envelope always carries ``data`` as an array: a route that responds
|
|
32
|
+
with one object (a persona, a created deployment, a signed download URL)
|
|
33
|
+
arrives as ``[obj]``.
|
|
34
|
+
"""
|
|
35
|
+
data = body.get("data")
|
|
36
|
+
if not isinstance(data, list):
|
|
37
|
+
raise HyperStudyError(
|
|
38
|
+
f"Unexpected response: expected 'data' to be an array, got {type(data).__name__}"
|
|
39
|
+
)
|
|
40
|
+
if not data or data[0] is None:
|
|
41
|
+
raise HyperStudyError("Unexpected response: the server returned no record")
|
|
42
|
+
return data[0]
|
|
43
|
+
|
|
44
|
+
|
|
28
45
|
class HttpTransport:
|
|
29
46
|
"""Thin wrapper around ``requests.Session`` for the HyperStudy API.
|
|
30
47
|
|
|
@@ -17,9 +17,11 @@ class DataType(str, Enum):
|
|
|
17
17
|
EVENTS = "events"
|
|
18
18
|
RECORDINGS = "recordings"
|
|
19
19
|
CHAT = "chat"
|
|
20
|
+
VOICE = "voice"
|
|
20
21
|
VIDEOCHAT = "videochat"
|
|
21
22
|
SYNC = "sync"
|
|
22
23
|
RATINGS = "ratings"
|
|
24
|
+
EYETRACKING = "eyetracking"
|
|
23
25
|
COMPONENTS = "components"
|
|
24
26
|
PARTICIPANTS = "participants"
|
|
25
27
|
ROOMS = "rooms"
|