interhumanai 1.2.2__tar.gz → 1.3.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.
- {interhumanai-1.2.2 → interhumanai-1.3.0}/CHANGELOG.md +16 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/PKG-INFO +15 -8
- {interhumanai-1.2.2 → interhumanai-1.3.0}/README.md +14 -7
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/__init__.py +2 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/_version.py +1 -1
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/auth.py +7 -7
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/client.py +1 -1
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/types.py +24 -1
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/upload.py +23 -11
- {interhumanai-1.2.2 → interhumanai-1.3.0}/.gitignore +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/LICENSE +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/pyproject.toml +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/_http.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/_sdk_metadata.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/_session_socket.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/config.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/errors.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/py.typed +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/realtime.py +0 -0
- {interhumanai-1.2.2 → interhumanai-1.3.0}/src/interhumanai/stream.py +0 -0
|
@@ -5,6 +5,22 @@ All notable changes to `interhumanai` are documented here. The package follows
|
|
|
5
5
|
the TypeScript SDK (`@interhumanai/sdk`): both packages always carry the same
|
|
6
6
|
version and release together.
|
|
7
7
|
|
|
8
|
+
## 1.3.0
|
|
9
|
+
|
|
10
|
+
- Added the whole-file signal list to upload jobs. ``submit(include=...)``
|
|
11
|
+
now takes ``UploadJobIncludeFlag``: the two ``IncludeFlag`` Conversation
|
|
12
|
+
Quality Index values plus ``SIGNALS`` (``IncludeFlag`` members still work).
|
|
13
|
+
With ``SIGNALS``, a completed job's ``UploadJobResult.signals`` carries every
|
|
14
|
+
window's signals as one chronological list, same-type signals in adjacent
|
|
15
|
+
windows merged into one span that keeps the highest probability and its
|
|
16
|
+
rationale, with no ``modality``. ``None`` without the flag; an empty list
|
|
17
|
+
when requested and nothing was detected. The ``windows`` are unchanged.
|
|
18
|
+
|
|
19
|
+
## 1.2.3
|
|
20
|
+
|
|
21
|
+
- Revised the reference documentation for authentication. Documentation only;
|
|
22
|
+
no behavior change.
|
|
23
|
+
|
|
8
24
|
## 1.2.2
|
|
9
25
|
|
|
10
26
|
- Corrected the reference docs for ``SessionConfigOptions.model``: it lists the models the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: interhumanai
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: First-party Python SDK for the Interhuman API: upload and stream social-signal analysis.
|
|
5
5
|
Project-URL: Homepage, https://docs.interhuman.ai
|
|
6
6
|
Project-URL: Repository, https://github.com/interhumanai/interhuman-api
|
|
@@ -144,12 +144,13 @@ job**: the API validates the file, answers with a job envelope, and analyzes
|
|
|
144
144
|
the file afterwards. Poll the job until it is `completed` or `failed`:
|
|
145
145
|
|
|
146
146
|
```python
|
|
147
|
-
from interhumanai import
|
|
147
|
+
from interhumanai import UploadJobIncludeFlag, UploadModel
|
|
148
148
|
|
|
149
149
|
job = await client.upload.submit(
|
|
150
150
|
"meeting.mp4",
|
|
151
151
|
model=UploadModel.INTER_2,
|
|
152
|
-
|
|
152
|
+
# optional whole-file views: quality scores and the merged signal list
|
|
153
|
+
include=[UploadJobIncludeFlag.CONVERSATION_QUALITY_OVERALL, UploadJobIncludeFlag.SIGNALS],
|
|
153
154
|
)
|
|
154
155
|
print(job.job_id, job.status.value) # queued
|
|
155
156
|
|
|
@@ -158,6 +159,8 @@ if job.status.value == "completed":
|
|
|
158
159
|
for window in job.result.windows:
|
|
159
160
|
names = ", ".join(signal.type.value for signal in window.signals)
|
|
160
161
|
print(f"{window.start_seconds:5.1f}s {window.engagement_status.value:11} {names}")
|
|
162
|
+
for signal in job.result.signals:
|
|
163
|
+
print(signal.type.value, signal.start, signal.end)
|
|
161
164
|
print(job.result.conversation_quality.overall.quality_index)
|
|
162
165
|
else:
|
|
163
166
|
print(job.error.error_id, job.error.message)
|
|
@@ -166,11 +169,15 @@ else:
|
|
|
166
169
|
- `submit(file, model=..., wait_seconds=0, include=None)` — pass `wait_seconds`
|
|
167
170
|
to let the API hold the request open for up to that long and return the
|
|
168
171
|
finished job inline when it completes in time; otherwise it returns the
|
|
169
|
-
queued envelope. Pass `include` with
|
|
170
|
-
|
|
171
|
-
`
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
queued envelope. Pass `include` with `UploadJobIncludeFlag` values to have
|
|
173
|
+
the result carry whole-file views. The two Conversation Quality Index values
|
|
174
|
+
(the same ones `analyze` takes as `IncludeFlag`, whose members also work
|
|
175
|
+
here) add `conversation_quality`, an `UploadJobConversationQuality` scored
|
|
176
|
+
over the whole file with `overall` and/or `timeline` set for the flags you
|
|
177
|
+
named. `SIGNALS` adds `signals`: every window's signals as one chronological
|
|
178
|
+
list, same-type signals in adjacent windows merged into one span with the
|
|
179
|
+
highest probability and its rationale, and no `modality` (an empty list when
|
|
180
|
+
nothing was detected). Without a flag the result has no such section.
|
|
174
181
|
- `get_job(job_id)` — one status read. Raises `InterhumanAPIError` with
|
|
175
182
|
`error_id` `ih4021` once the job has expired (`job.expires_at`).
|
|
176
183
|
- `wait_for_job(job_id, timeout=None, poll_interval=2.0)` — returns the
|
|
@@ -118,12 +118,13 @@ job**: the API validates the file, answers with a job envelope, and analyzes
|
|
|
118
118
|
the file afterwards. Poll the job until it is `completed` or `failed`:
|
|
119
119
|
|
|
120
120
|
```python
|
|
121
|
-
from interhumanai import
|
|
121
|
+
from interhumanai import UploadJobIncludeFlag, UploadModel
|
|
122
122
|
|
|
123
123
|
job = await client.upload.submit(
|
|
124
124
|
"meeting.mp4",
|
|
125
125
|
model=UploadModel.INTER_2,
|
|
126
|
-
|
|
126
|
+
# optional whole-file views: quality scores and the merged signal list
|
|
127
|
+
include=[UploadJobIncludeFlag.CONVERSATION_QUALITY_OVERALL, UploadJobIncludeFlag.SIGNALS],
|
|
127
128
|
)
|
|
128
129
|
print(job.job_id, job.status.value) # queued
|
|
129
130
|
|
|
@@ -132,6 +133,8 @@ if job.status.value == "completed":
|
|
|
132
133
|
for window in job.result.windows:
|
|
133
134
|
names = ", ".join(signal.type.value for signal in window.signals)
|
|
134
135
|
print(f"{window.start_seconds:5.1f}s {window.engagement_status.value:11} {names}")
|
|
136
|
+
for signal in job.result.signals:
|
|
137
|
+
print(signal.type.value, signal.start, signal.end)
|
|
135
138
|
print(job.result.conversation_quality.overall.quality_index)
|
|
136
139
|
else:
|
|
137
140
|
print(job.error.error_id, job.error.message)
|
|
@@ -140,11 +143,15 @@ else:
|
|
|
140
143
|
- `submit(file, model=..., wait_seconds=0, include=None)` — pass `wait_seconds`
|
|
141
144
|
to let the API hold the request open for up to that long and return the
|
|
142
145
|
finished job inline when it completes in time; otherwise it returns the
|
|
143
|
-
queued envelope. Pass `include` with
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
queued envelope. Pass `include` with `UploadJobIncludeFlag` values to have
|
|
147
|
+
the result carry whole-file views. The two Conversation Quality Index values
|
|
148
|
+
(the same ones `analyze` takes as `IncludeFlag`, whose members also work
|
|
149
|
+
here) add `conversation_quality`, an `UploadJobConversationQuality` scored
|
|
150
|
+
over the whole file with `overall` and/or `timeline` set for the flags you
|
|
151
|
+
named. `SIGNALS` adds `signals`: every window's signals as one chronological
|
|
152
|
+
list, same-type signals in adjacent windows merged into one span with the
|
|
153
|
+
highest probability and its rationale, and no `modality` (an empty list when
|
|
154
|
+
nothing was detected). Without a flag the result has no such section.
|
|
148
155
|
- `get_job(job_id)` — one status read. Raises `InterhumanAPIError` with
|
|
149
156
|
`error_id` `ih4021` once the job has expired (`job.expires_at`).
|
|
150
157
|
- `wait_for_job(job_id, timeout=None, poll_interval=2.0)` — returns the
|
|
@@ -113,6 +113,7 @@ from .types import (
|
|
|
113
113
|
UploadJob,
|
|
114
114
|
UploadJobConversationQuality,
|
|
115
115
|
UploadJobError,
|
|
116
|
+
UploadJobIncludeFlag,
|
|
116
117
|
UploadJobResult,
|
|
117
118
|
UploadJobSignal,
|
|
118
119
|
UploadJobStatus,
|
|
@@ -214,6 +215,7 @@ __all__ = [
|
|
|
214
215
|
"UploadJob",
|
|
215
216
|
"UploadJobConversationQuality",
|
|
216
217
|
"UploadJobError",
|
|
218
|
+
"UploadJobIncludeFlag",
|
|
217
219
|
"UploadJobResult",
|
|
218
220
|
"UploadJobSignal",
|
|
219
221
|
"UploadJobStatus",
|
|
@@ -20,7 +20,7 @@ DEFAULT_REFRESH_SKEW_SECONDS = 30.0
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
class TokenResponse(BaseModel):
|
|
23
|
-
"""Response of
|
|
23
|
+
"""Response of a token request."""
|
|
24
24
|
|
|
25
25
|
access_token: str
|
|
26
26
|
token_type: str
|
|
@@ -48,7 +48,7 @@ def _scope_values(scopes: Sequence[Scope | str]) -> list[str]:
|
|
|
48
48
|
|
|
49
49
|
|
|
50
50
|
class AuthClient:
|
|
51
|
-
"""Client for the token endpoints (``/v1/
|
|
51
|
+
"""Client for the client-token endpoints (``/v1/client_tokens``).
|
|
52
52
|
|
|
53
53
|
Args:
|
|
54
54
|
environment: Named environment to call. Defaults to ``production``.
|
|
@@ -89,7 +89,7 @@ class AuthClient:
|
|
|
89
89
|
key_secret: str,
|
|
90
90
|
scopes: Sequence[Scope | str],
|
|
91
91
|
) -> TokenResponse:
|
|
92
|
-
"""
|
|
92
|
+
"""Authenticate with API key credentials for a short-lived session.
|
|
93
93
|
|
|
94
94
|
Args:
|
|
95
95
|
key_id: The API key id.
|
|
@@ -163,10 +163,10 @@ class AuthClient:
|
|
|
163
163
|
|
|
164
164
|
|
|
165
165
|
class TokenProvider(Protocol):
|
|
166
|
-
"""Anything that can produce a bearer
|
|
166
|
+
"""Anything that can produce a bearer credential on demand."""
|
|
167
167
|
|
|
168
168
|
async def get_token(self) -> str:
|
|
169
|
-
"""Return a currently valid bearer
|
|
169
|
+
"""Return a currently valid bearer credential."""
|
|
170
170
|
...
|
|
171
171
|
|
|
172
172
|
|
|
@@ -177,7 +177,7 @@ class StaticTokenProvider:
|
|
|
177
177
|
"""Store the token to hand out.
|
|
178
178
|
|
|
179
179
|
Args:
|
|
180
|
-
token: The
|
|
180
|
+
token: The client token or API key to return from :meth:`get_token`.
|
|
181
181
|
"""
|
|
182
182
|
self._token = token
|
|
183
183
|
|
|
@@ -224,7 +224,7 @@ class TokenManager:
|
|
|
224
224
|
self._lock = asyncio.Lock()
|
|
225
225
|
|
|
226
226
|
async def get_token(self) -> str:
|
|
227
|
-
"""Return a valid
|
|
227
|
+
"""Return a valid bearer credential, renewing it when needed."""
|
|
228
228
|
async with self._lock:
|
|
229
229
|
if self._token is not None and self._clock() < self._refresh_at:
|
|
230
230
|
return self._token
|
|
@@ -35,7 +35,7 @@ class InterhumanClient:
|
|
|
35
35
|
Args:
|
|
36
36
|
key_id: API key id (paired with ``key_secret``).
|
|
37
37
|
key_secret: API key secret.
|
|
38
|
-
access_token:
|
|
38
|
+
access_token: A client token, or your API key, used as-is.
|
|
39
39
|
scopes: Scopes requested when exchanging credentials. Defaults to
|
|
40
40
|
upload + stream. Ignored when ``access_token`` is used.
|
|
41
41
|
environment: Named environment to call. Defaults to ``production``.
|
|
@@ -226,6 +226,20 @@ class UploadModel(str, enum.Enum):
|
|
|
226
226
|
INTER_2_DEEP = "inter-2-deep"
|
|
227
227
|
|
|
228
228
|
|
|
229
|
+
class UploadJobIncludeFlag(str, enum.Enum):
|
|
230
|
+
"""Whole-file sections a ``POST /v2/upload/analyze`` job's result can carry.
|
|
231
|
+
|
|
232
|
+
The two :class:`IncludeFlag` Conversation Quality Index sections, plus
|
|
233
|
+
``SIGNALS`` for the whole-file signal list
|
|
234
|
+
(:attr:`UploadJobResult.signals`). ``SIGNALS`` is a v2 upload option only;
|
|
235
|
+
:meth:`~interhumanai.UploadClient.analyze` and the stream do not take it.
|
|
236
|
+
"""
|
|
237
|
+
|
|
238
|
+
CONVERSATION_QUALITY_OVERALL = "conversation_quality_overall"
|
|
239
|
+
CONVERSATION_QUALITY_TIMELINE = "conversation_quality_timeline"
|
|
240
|
+
SIGNALS = "signals"
|
|
241
|
+
|
|
242
|
+
|
|
229
243
|
class UploadJobStatus(str, enum.Enum):
|
|
230
244
|
"""Lifecycle of a ``POST /v2/upload/analyze`` job.
|
|
231
245
|
|
|
@@ -285,13 +299,22 @@ class UploadJobConversationQuality(BaseModel):
|
|
|
285
299
|
class UploadJobResult(BaseModel):
|
|
286
300
|
"""The result of a completed upload job: one entry per analyzed window.
|
|
287
301
|
|
|
302
|
+
``signals`` is every window's signals as one list over the whole file, in
|
|
303
|
+
chronological order, merged by the same rules as
|
|
304
|
+
:meth:`~interhumanai.UploadClient.analyze` and with no ``modality``:
|
|
305
|
+
signals of the same type in adjacent windows become one span, which keeps
|
|
306
|
+
the highest probability and that signal's rationale. It is set only when the submit
|
|
307
|
+
passed :attr:`UploadJobIncludeFlag.SIGNALS`, and is then an empty list when
|
|
308
|
+
nothing was detected; ``windows`` are unchanged either way.
|
|
309
|
+
|
|
288
310
|
``conversation_quality`` is present only when the submit passed at least one
|
|
289
|
-
|
|
311
|
+
Conversation Quality Index flag, and carries the sections those flags named.
|
|
290
312
|
"""
|
|
291
313
|
|
|
292
314
|
duration_seconds: float
|
|
293
315
|
window_seconds: float
|
|
294
316
|
windows: list[UploadJobWindow] = Field(default_factory=list)
|
|
317
|
+
signals: list[UploadJobSignal] | None = None
|
|
295
318
|
conversation_quality: UploadJobConversationQuality | None = None
|
|
296
319
|
|
|
297
320
|
|
|
@@ -13,7 +13,14 @@ from ._http import request_json
|
|
|
13
13
|
from .auth import TokenProvider
|
|
14
14
|
from .config import Environment, resolve_http_base_url
|
|
15
15
|
from .errors import UploadJobTimeoutError
|
|
16
|
-
from .types import
|
|
16
|
+
from .types import (
|
|
17
|
+
AnalysisResult,
|
|
18
|
+
GoalDimension,
|
|
19
|
+
IncludeFlag,
|
|
20
|
+
UploadJob,
|
|
21
|
+
UploadJobIncludeFlag,
|
|
22
|
+
UploadModel,
|
|
23
|
+
)
|
|
17
24
|
|
|
18
25
|
DEFAULT_JOB_POLL_INTERVAL_SECONDS = 2.0
|
|
19
26
|
"""How often :meth:`UploadClient.wait_for_job` reads the job status by default."""
|
|
@@ -119,7 +126,7 @@ class UploadClient:
|
|
|
119
126
|
*,
|
|
120
127
|
model: UploadModel | str,
|
|
121
128
|
wait_seconds: int = 0,
|
|
122
|
-
include: Sequence[IncludeFlag | str] | None = None,
|
|
129
|
+
include: Sequence[UploadJobIncludeFlag | IncludeFlag | str] | None = None,
|
|
123
130
|
filename: str | None = None,
|
|
124
131
|
content_type: str | None = None,
|
|
125
132
|
) -> UploadJob:
|
|
@@ -132,10 +139,11 @@ class UploadClient:
|
|
|
132
139
|
it comes back ``queued`` or ``running`` and :meth:`wait_for_job` or
|
|
133
140
|
:meth:`get_job` reads it later.
|
|
134
141
|
|
|
135
|
-
Pass ``include`` to have the completed job's result carry
|
|
136
|
-
Conversation Quality Index
|
|
137
|
-
|
|
138
|
-
|
|
142
|
+
Pass ``include`` to have the completed job's result carry whole-file
|
|
143
|
+
views: the Conversation Quality Index in the sections the CQI flags
|
|
144
|
+
name (:attr:`UploadJobResult.conversation_quality`), and the merged
|
|
145
|
+
signal list for ``SIGNALS`` (:attr:`UploadJobResult.signals`). Without
|
|
146
|
+
a flag the result carries no such section.
|
|
139
147
|
|
|
140
148
|
For ``UploadModel.INTER_2`` the file is an mp4, mov, avi, mkv, webm or
|
|
141
149
|
mpeg-ts video carrying both a video and an audio track (``ih5001``
|
|
@@ -151,10 +159,13 @@ class UploadClient:
|
|
|
151
159
|
wait_seconds: How long the API may hold the request waiting for
|
|
152
160
|
the job to finish. ``0`` (the default) answers at once. The
|
|
153
161
|
deployment bounds it; a larger value raises ``ih4005``.
|
|
154
|
-
include:
|
|
155
|
-
|
|
156
|
-
``
|
|
157
|
-
|
|
162
|
+
include: Whole-file sections the result should carry, as
|
|
163
|
+
:class:`UploadJobIncludeFlag` values:
|
|
164
|
+
``CONVERSATION_QUALITY_OVERALL`` and/or
|
|
165
|
+
``CONVERSATION_QUALITY_TIMELINE`` (the same values
|
|
166
|
+
:meth:`analyze` takes, so :class:`IncludeFlag` members work
|
|
167
|
+
too) for the quality block, and ``SIGNALS`` for the signal
|
|
168
|
+
list. Omit a flag for no such section.
|
|
158
169
|
filename: Filename reported to the API. Defaults to the path or
|
|
159
170
|
file object's name, else ``video``.
|
|
160
171
|
content_type: MIME type of the file (e.g. ``video/mp4``).
|
|
@@ -172,7 +183,8 @@ class UploadClient:
|
|
|
172
183
|
data["wait_seconds"] = str(wait_seconds)
|
|
173
184
|
if include:
|
|
174
185
|
data["include[]"] = [
|
|
175
|
-
flag.value if isinstance(flag, IncludeFlag) else flag
|
|
186
|
+
flag.value if isinstance(flag, (IncludeFlag, UploadJobIncludeFlag)) else flag
|
|
187
|
+
for flag in include
|
|
176
188
|
]
|
|
177
189
|
headers = {"Authorization": f"Bearer {token}"}
|
|
178
190
|
url = self._base_url + "/v2/upload/analyze"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|