olbrain-python-sdk 0.3.2__py3-none-any.whl → 0.5.1__py3-none-any.whl
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.
- olbrain/AGENTS.md +163 -64
- olbrain/__init__.py +12 -4
- olbrain/client.py +46 -205
- olbrain/exceptions.py +22 -0
- olbrain/olbrain.py +242 -0
- olbrain/research.py +200 -0
- olbrain/streaming.py +57 -105
- {olbrain_python_sdk-0.3.2.dist-info → olbrain_python_sdk-0.5.1.dist-info}/METADATA +35 -29
- olbrain_python_sdk-0.5.1.dist-info/RECORD +14 -0
- {olbrain_python_sdk-0.3.2.dist-info → olbrain_python_sdk-0.5.1.dist-info}/WHEEL +1 -1
- olbrain_python_sdk-0.3.2.dist-info/RECORD +0 -12
- {olbrain_python_sdk-0.3.2.dist-info → olbrain_python_sdk-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {olbrain_python_sdk-0.3.2.dist-info → olbrain_python_sdk-0.5.1.dist-info}/top_level.txt +0 -0
olbrain/AGENTS.md
CHANGED
|
@@ -7,9 +7,20 @@ taken from the source. If it disagrees with the web docs, this file is right.
|
|
|
7
7
|
Find it from Python with:
|
|
8
8
|
`python -c "import olbrain, os; print(os.path.join(os.path.dirname(olbrain.__file__), 'AGENTS.md'))"`
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
## Pick the right client
|
|
11
|
+
|
|
12
|
+
| You want to… | Use | Auth |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| Chat with a conversational agent (send a message, get a reply) | `AgentClient` | `api_key="ak_…"` (the only kind issued today; `sk_live_`, `org_live_`, `sk_` and `org_` still pass validation) |
|
|
15
|
+
| Trigger workflow agents, read/control runs, resolve approvals | `Olbrain` | `api_key="ak_…"` **or** `get_id_token` (Olbrain sign-in) |
|
|
16
|
+
| Drive a research agent (sessions, streamed turns, plans, reports) | `Olbrain.research` | `get_id_token` only. An `ak_` key gets `NotFoundError` (`agent_not_found`) |
|
|
17
|
+
|
|
18
|
+
These are two separate HTTP clients with different error rules. Both default
|
|
19
|
+
to `https://webhook.olbrain.com`. The JavaScript SDK (`@olbrain/js-sdk`) has
|
|
20
|
+
the same two clients plus a browser `ChatWidget`.
|
|
21
|
+
|
|
22
|
+
Keep the `ak_` key on your server (an environment variable such as
|
|
23
|
+
`OLBRAIN_API_KEY`). Never send it to a browser.
|
|
13
24
|
|
|
14
25
|
## Install and import
|
|
15
26
|
|
|
@@ -18,10 +29,14 @@ pip install olbrain-python-sdk # import name: olbrain. Requires Python 3.7+ an
|
|
|
18
29
|
```
|
|
19
30
|
|
|
20
31
|
```python
|
|
21
|
-
from olbrain import AgentClient,
|
|
32
|
+
from olbrain import AgentClient, Olbrain, OlbrainError
|
|
22
33
|
```
|
|
23
34
|
|
|
24
|
-
|
|
35
|
+
Both clients are synchronous and use `requests`.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 1. `AgentClient` — conversational agents
|
|
25
40
|
|
|
26
41
|
```python
|
|
27
42
|
import os
|
|
@@ -36,57 +51,38 @@ with AgentClient(agent_id="your-agent-id", api_key=os.environ["OLBRAIN_API_KEY"]
|
|
|
36
51
|
print("agent error:", reply.error)
|
|
37
52
|
```
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
## `AgentClient(agent_id, api_key, agent_url=None)`
|
|
54
|
+
### `AgentClient(agent_id, api_key, agent_url=None)`
|
|
42
55
|
|
|
43
56
|
- `agent_id`: required. An empty value raises `ValueError`.
|
|
44
|
-
- `api_key`: must start with `sk_live_`, `org_live_`, `sk_` or `org_`,
|
|
45
|
-
constructor raises `ValueError`.
|
|
46
|
-
- `agent_url`: optional. Defaults to `
|
|
57
|
+
- `api_key`: must start with `ak_`, `sk_live_`, `org_live_`, `sk_` or `org_`,
|
|
58
|
+
or the constructor raises `ValueError`.
|
|
59
|
+
- `agent_url`: optional. Defaults to the `OLBRAIN_AGENT_URL` environment
|
|
60
|
+
variable if set, otherwise `https://webhook.olbrain.com`.
|
|
47
61
|
|
|
48
|
-
|
|
49
|
-
`Authorization: Bearer <api_key
|
|
50
|
-
|
|
62
|
+
Chat calls are `POST {agent_url}/api/agents/webhook/{agent_id}` with
|
|
63
|
+
`Authorization: Bearer <api_key>`. Session reads are
|
|
64
|
+
`GET {agent_url}/api/agents/{agent_id}/sessions/{session_id}[/messages]`. The
|
|
65
|
+
client is a context manager, and `__exit__` calls `close()`.
|
|
51
66
|
|
|
52
67
|
### Methods
|
|
53
68
|
|
|
54
69
|
| Method | Returns | Notes |
|
|
55
70
|
|---|---|---|
|
|
56
|
-
| `create_session(on_message=None, title=None, user_id=None, metadata=None, mode="production", description=None)` | `str` session id | Sends `title` (default `"New Session"`) to the agent **as the first message**. `mode` must be `"development"`, `"testing"` or `"production"`, otherwise `ValueError
|
|
71
|
+
| `create_session(on_message=None, title=None, user_id=None, metadata=None, mode="production", description=None)` | `str` session id | Sends `title` (default `"New Session"`) to the agent **as the first message**. `mode` must be `"development"`, `"testing"` or `"production"`, otherwise `ValueError`, but it is not sent: the API always serves the published agent. `description` is stored as `metadata["session_description"]`. If you pass `on_message`, it also calls `listen()`. Timeout 30 s. |
|
|
57
72
|
| `send_and_wait(session_id, message, user_id=None, metadata=None, model=None, timeout=120)` | `ChatResponse` | Waits for the reply. `timeout` is in **seconds**. `model` overrides the agent's model. Empty or whitespace-only messages raise `ValueError`. |
|
|
58
73
|
| `send(session_id, message, user_id=None, metadata=None, model=None, mode="production")` | `dict` (raw JSON) | Same request, but returns the raw response dict (`response`, `token_usage`, `success`, …). It still blocks, with a 120 s timeout. |
|
|
59
|
-
| `listen(session_id, on_message)` | `None` | Starts a background daemon thread that
|
|
74
|
+
| `listen(session_id, on_message)` | `None` | Starts a background daemon thread that polls the session's messages every 2 s and calls `on_message` once per **new** message (messages already there are skipped). Calling it again for the same session does nothing. |
|
|
60
75
|
| `run()` | `None` | Blocks until Ctrl+C, then calls `close()`. Use it to keep a script alive for `listen` callbacks. |
|
|
61
|
-
| `close()` | `None` | Stops all
|
|
62
|
-
| `get_session(session_id)` | `SessionInfo` |
|
|
63
|
-
| `
|
|
64
|
-
| `
|
|
65
|
-
| `
|
|
66
|
-
| `get_messages(session_id, limit=20, offset=0)` | `dict` | The messages list and pagination info, as the backend returns them. |
|
|
67
|
-
|
|
68
|
-
The five session-management methods (`get_session` through `get_messages`)
|
|
69
|
-
depend on webhook `action`s that not every backend implements. They raise if
|
|
70
|
-
the backend refuses. Only `create_session`, `send_and_wait` and `send` are
|
|
71
|
-
guaranteed to work.
|
|
76
|
+
| `close()` | `None` | Stops all listeners. |
|
|
77
|
+
| `get_session(session_id)` | `SessionInfo` | `SessionNotFoundError` if it doesn't exist (or belongs to another agent). |
|
|
78
|
+
| `get_session_stats(session_id)` | `dict` | `message_count`, `token_usage`, `cost`, `created_at`, `last_message_at`. |
|
|
79
|
+
| `get_messages(session_id, limit=20, offset=0)` | `dict` | `{"messages": [...], "limit", "offset"}`, oldest first; `limit` at most 200. |
|
|
80
|
+
| `update_session(...)`, `delete_session(...)` | — | Always raise `OlbrainError`: the API has no endpoint for them. |
|
|
72
81
|
|
|
73
|
-
|
|
82
|
+
`on_message` gets each message as a raw dict (`role`, `content`, …). It runs
|
|
83
|
+
on the listener thread, so keep it fast and don't raise from it.
|
|
74
84
|
|
|
75
|
-
|
|
76
|
-
key. Frames whose `type` is `ping` or `connected` are skipped. The callback
|
|
77
|
-
runs on the stream thread, so keep it fast and don't raise from it.
|
|
78
|
-
|
|
79
|
-
```python
|
|
80
|
-
def on_message(msg: dict):
|
|
81
|
-
print(f"{msg.get('role')}: {msg['content']}")
|
|
82
|
-
|
|
83
|
-
client = AgentClient(agent_id="…", api_key="sk_live_…")
|
|
84
|
-
sid = client.create_session(on_message=on_message, title="Streaming chat")
|
|
85
|
-
client.send(sid, "Tell me a story")
|
|
86
|
-
client.run() # blocks; Ctrl+C to stop
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Data classes (`olbrain.session`, re-exported from `olbrain`)
|
|
85
|
+
### Data classes (`olbrain.session`, re-exported from `olbrain`)
|
|
90
86
|
|
|
91
87
|
```python
|
|
92
88
|
@dataclass
|
|
@@ -126,12 +122,9 @@ class SessionInfo:
|
|
|
126
122
|
`reply.success` too. A 200 response can carry `success=False` and an `error`.
|
|
127
123
|
|
|
128
124
|
Avoid the exported `Message` and `Session` classes. Nothing in the client
|
|
129
|
-
returns them
|
|
130
|
-
|
|
131
|
-
## Errors (`olbrain.exceptions`)
|
|
125
|
+
returns them.
|
|
132
126
|
|
|
133
|
-
|
|
134
|
-
`.details`.
|
|
127
|
+
### Errors (`AgentClient`)
|
|
135
128
|
|
|
136
129
|
| Condition | Raised |
|
|
137
130
|
|---|---|
|
|
@@ -143,25 +136,131 @@ All of them subclass `OlbrainError`, which has `.message`, `.error_code` and
|
|
|
143
136
|
| Any other non-200 (403, 5xx…) | `OlbrainError("Failed to <operation>: <status> - <detail>")` |
|
|
144
137
|
| Connection error or timeout | `NetworkError` |
|
|
145
138
|
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 2. `Olbrain` — workflow agents, runs, approvals
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"]) # server / script
|
|
145
|
+
# olbrain = Olbrain(get_id_token=lambda: current_user_id_token()) # signed-in Olbrain user (Firebase ID token)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`Olbrain(api_key=None, get_id_token=None, base_url=None, timeout=30)`:
|
|
149
|
+
`api_key` must start `ak_`; `get_id_token` is a callable returning the
|
|
150
|
+
token and is called before **every** request; `base_url` defaults to the
|
|
151
|
+
`OLBRAIN_AGENT_URL` environment variable if set, otherwise
|
|
152
|
+
`https://webhook.olbrain.com`; `timeout` is in seconds. Give **exactly one** of
|
|
153
|
+
`api_key` or `get_id_token`, or the constructor raises `ValidationError`.
|
|
154
|
+
|
|
155
|
+
Every method takes the agent id first and returns the API's JSON (snake_case
|
|
156
|
+
dicts and lists).
|
|
157
|
+
|
|
158
|
+
| Method | HTTP | Returns |
|
|
159
|
+
|---|---|---|
|
|
160
|
+
| `workflows.run(agent_id, payload=None, variables=None)` | `POST /api/workflows/{agent_id}/trigger` | `{"run_id", "status"}` |
|
|
161
|
+
| `runs.list(agent_id, status=None, limit=None)` | `GET /api/agents/{agent_id}/runs` | list of runs |
|
|
162
|
+
| `runs.get(agent_id, run_id)` | `GET …/runs/{run_id}` | run |
|
|
163
|
+
| `runs.cancel / pause / retry / resume(agent_id, run_id)` | `POST …/runs/{run_id}/{verb}` | dict |
|
|
164
|
+
| `approvals.list(agent_id, run_id)` | `GET …/runs/{run_id}/approvals` | list of approvals |
|
|
165
|
+
| `approvals.resolve(agent_id, run_id, exception_id, resolution, modified_data=None)` | `POST …/approvals/resolve` | `{"exception_id", "resolution", "all_resolved", "resumed"}` |
|
|
166
|
+
|
|
167
|
+
A run has `run_id`, `status`, and optionally `progress_percent`, `started_at`,
|
|
168
|
+
`completed_at`, `duration_seconds`, `error` and `summary`
|
|
169
|
+
(`total_processed`, `successful`, `failed`, `exceptions`, `skipped`). An
|
|
170
|
+
approval has `exception_id`, `step_id`, `item_id`, `data`, `error`,
|
|
171
|
+
`requires_human_review` and `blocking`. `resolution` is `"approved"`,
|
|
172
|
+
`"rejected"` or `"modified"`; send `modified_data` only with `"modified"`.
|
|
173
|
+
|
|
174
|
+
There is no SDK helper for waiting on a run, so poll `runs.get` yourself:
|
|
175
|
+
|
|
146
176
|
```python
|
|
147
|
-
|
|
148
|
-
import
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
177
|
+
import os, time
|
|
178
|
+
from olbrain import Olbrain
|
|
179
|
+
|
|
180
|
+
olbrain = Olbrain(api_key=os.environ["OLBRAIN_API_KEY"])
|
|
181
|
+
run_id = olbrain.workflows.run(agent_id, payload={"url": url})["run_id"]
|
|
182
|
+
DONE = {"completed", "failed", "canceled", "cancelled"}
|
|
183
|
+
run = olbrain.runs.get(agent_id, run_id)
|
|
184
|
+
while run["status"] not in DONE:
|
|
185
|
+
for a in olbrain.approvals.list(agent_id, run_id):
|
|
186
|
+
olbrain.approvals.resolve(agent_id, run_id, a["exception_id"], "approved")
|
|
187
|
+
time.sleep(3)
|
|
188
|
+
run = olbrain.runs.get(agent_id, run_id)
|
|
159
189
|
```
|
|
160
190
|
|
|
161
|
-
|
|
162
|
-
|
|
191
|
+
The terminal status strings in that loop are an assumption. Check the values
|
|
192
|
+
your agent actually returns.
|
|
193
|
+
|
|
194
|
+
### Errors (`Olbrain`, including `research`)
|
|
195
|
+
|
|
196
|
+
The error code and message come from the response body: `{error:{code,message}}`
|
|
197
|
+
or FastAPI's `{detail}`.
|
|
198
|
+
|
|
199
|
+
| Condition | Raised |
|
|
200
|
+
|---|---|
|
|
201
|
+
| code is `insufficient_funds`, `credit_limit_exceeded`, `subscription_suspended`, `subscription_inactive` or `billing_unavailable` (any status) | `BillingError` (`.status`, `.code`) |
|
|
202
|
+
| 401 | `AuthenticationError` |
|
|
203
|
+
| 404 | `NotFoundError` (`.status = 404`, `.code`, e.g. `agent_not_found`) |
|
|
204
|
+
| 429 | `RateLimitError` (`.retry_after` seconds from the `Retry-After` header, or `None`) |
|
|
205
|
+
| any other non-2xx (403, 409, 422, 5xx…) | `ApiError` (`.status`, `.code`, `.message`, `.detail` = raw `detail`) |
|
|
206
|
+
| connection failure, timeout, or a dropped stream | `NetworkError` |
|
|
207
|
+
|
|
208
|
+
`BillingError` and `NotFoundError` subclass `ApiError`, and every error class
|
|
209
|
+
subclasses `OlbrainError`, so put the narrower `except` clauses first.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## 3. `Olbrain.research` — research agents (sign-ins only)
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
olbrain = Olbrain(get_id_token=lambda: user_id_token)
|
|
217
|
+
r = olbrain.research
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
All paths sit under `/api/agents/{agent_id}/research`. The agent must be a
|
|
221
|
+
research agent that is bound to a screen in the user's org. Payloads are
|
|
222
|
+
returned untyped (dicts and lists) because their shape belongs to the
|
|
223
|
+
research-runtime service.
|
|
224
|
+
|
|
225
|
+
| Method | |
|
|
226
|
+
|---|---|
|
|
227
|
+
| `sessions.create(agent_id)` | session (has `session_id`) |
|
|
228
|
+
| `sessions.list(agent_id, include_archived=False)`, `sessions.get / delete(agent_id, session_id)` | |
|
|
229
|
+
| `sessions.patch(agent_id, session_id, patch)` | partial session fields, passed through |
|
|
230
|
+
| `sessions.models(agent_id, session_id)` | available models |
|
|
231
|
+
| `sessions.set_model / set_review_model(agent_id, session_id, model)` | `None` resets to the default |
|
|
232
|
+
| `sessions.stream_message(agent_id, session_id, content, attachments=None, effort=None, escalates_run_id=None, command=None, target_run_id=None)` | generator of frames |
|
|
233
|
+
| `plans.approve(agent_id, session_id, message_id, body=None)`, `plans.approve_by_id(agent_id, session_id, plan_id, body=None)` | |
|
|
234
|
+
| `attachments.add(agent_id, session_id, files)` | multipart upload of `(filename, file or bytes[, content_type])` tuples; returns refs to pass as `attachments` |
|
|
235
|
+
| `attachments.upload_url(agent_id, session_id, storage_path)` | `{"url"}` |
|
|
236
|
+
| `runs.cancel / retry(agent_id, run_id)`, `runs.active_job(agent_id, run_id)` (or `None`), `runs.resolve_claims(agent_id, run_id, resolutions)` | |
|
|
237
|
+
| `reports.get(agent_id, run_id, version=None)`, `reports.artifact_url(agent_id, run_id, profile_id, fmt)`, `reports.evidence(agent_id, run_id)` | |
|
|
238
|
+
| `reports.share.get / revoke(agent_id, run_id)`, `reports.share.set(agent_id, run_id, visibility)` | |
|
|
239
|
+
| `review.get(agent_id, run_id)`; `review.run(agent_id, run_id)`, `review.apply(agent_id, run_id, finding_ids)` | the last two block, up to 600 s |
|
|
240
|
+
| `review.stream(agent_id, run_id)`, `review.stream_apply(agent_id, run_id, finding_ids)` | generators of frames |
|
|
241
|
+
| `templates.versions(agent_id)` | |
|
|
242
|
+
| `messages.older(agent_id, session_id, before_ts)` | one page of older messages, oldest first |
|
|
243
|
+
|
|
244
|
+
Stream frames come straight from research-runtime, unchanged: `{"type": "text_delta", "text"}`,
|
|
245
|
+
`{"type": "tool_use_start", …}`, `{"type": "complete", …}` and `{"type": "error", …}`.
|
|
246
|
+
The request is sent when you start iterating, and it can wait up to 600 s for
|
|
247
|
+
its first frame while the agent searches. To cancel, stop iterating (`break`,
|
|
248
|
+
or `.close()` the generator); the connection is closed.
|
|
249
|
+
|
|
250
|
+
```python
|
|
251
|
+
for ev in r.sessions.stream_message(agent_id, sid, "Size the Indian EV charger market"):
|
|
252
|
+
if ev.get("type") == "text_delta":
|
|
253
|
+
print(ev["text"], end="", flush=True)
|
|
254
|
+
elif ev.get("type") == "error":
|
|
255
|
+
raise RuntimeError(ev.get("message", "research error"))
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
The JavaScript SDK's `live.*` polling subscriptions are not in Python. Poll
|
|
259
|
+
`sessions.get` / `reports.get` yourself.
|
|
260
|
+
|
|
261
|
+
---
|
|
163
262
|
|
|
164
263
|
## Logging
|
|
165
264
|
|
|
166
|
-
|
|
167
|
-
`logging.basicConfig(level=logging.DEBUG)` to see requests and
|
|
265
|
+
`AgentClient` logs through `logging.getLogger("olbrain")`. Turn on
|
|
266
|
+
`logging.basicConfig(level=logging.DEBUG)` to see requests and listener events.
|
olbrain/__init__.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Olbrain Python SDK
|
|
3
|
-
|
|
3
|
+
AgentClient for conversational agents; Olbrain for workflow runs, approvals and research agents
|
|
4
4
|
|
|
5
5
|
Full offline API reference: AGENTS.md next to this file.
|
|
6
6
|
|
|
@@ -23,6 +23,7 @@ Example:
|
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
25
|
from .client import AgentClient
|
|
26
|
+
from .olbrain import Olbrain
|
|
26
27
|
from .session import ChatResponse, SessionInfo, TokenUsage, Message, Session
|
|
27
28
|
from .exceptions import (
|
|
28
29
|
OlbrainError,
|
|
@@ -32,16 +33,20 @@ from .exceptions import (
|
|
|
32
33
|
SessionNotFoundError,
|
|
33
34
|
RateLimitError,
|
|
34
35
|
ValidationError,
|
|
35
|
-
StreamingError
|
|
36
|
+
StreamingError,
|
|
37
|
+
ApiError,
|
|
38
|
+
NotFoundError,
|
|
39
|
+
BillingError,
|
|
36
40
|
)
|
|
37
41
|
|
|
38
|
-
__version__ = "0.
|
|
42
|
+
__version__ = "0.5.1"
|
|
39
43
|
__author__ = "Olbrain Team"
|
|
40
44
|
__email__ = "support@olbrain.com"
|
|
41
45
|
|
|
42
46
|
__all__ = [
|
|
43
47
|
# Main client
|
|
44
48
|
'AgentClient',
|
|
49
|
+
'Olbrain',
|
|
45
50
|
# Data classes
|
|
46
51
|
'ChatResponse',
|
|
47
52
|
'SessionInfo',
|
|
@@ -56,5 +61,8 @@ __all__ = [
|
|
|
56
61
|
'SessionNotFoundError',
|
|
57
62
|
'RateLimitError',
|
|
58
63
|
'ValidationError',
|
|
59
|
-
'StreamingError'
|
|
64
|
+
'StreamingError',
|
|
65
|
+
'ApiError',
|
|
66
|
+
'NotFoundError',
|
|
67
|
+
'BillingError',
|
|
60
68
|
]
|