tex-sdk 0.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.
- tex_sdk-0.3.0/PKG-INFO +627 -0
- tex_sdk-0.3.0/README.md +616 -0
- tex_sdk-0.3.0/pyproject.toml +26 -0
- tex_sdk-0.3.0/setup.cfg +4 -0
- tex_sdk-0.3.0/tex/__init__.py +20 -0
- tex_sdk-0.3.0/tex/client.py +629 -0
- tex_sdk-0.3.0/tex/config.py +70 -0
- tex_sdk-0.3.0/tex/errors.py +29 -0
- tex_sdk-0.3.0/tex/py.typed +0 -0
- tex_sdk-0.3.0/tex_sdk.egg-info/PKG-INFO +627 -0
- tex_sdk-0.3.0/tex_sdk.egg-info/SOURCES.txt +12 -0
- tex_sdk-0.3.0/tex_sdk.egg-info/dependency_links.txt +1 -0
- tex_sdk-0.3.0/tex_sdk.egg-info/requires.txt +4 -0
- tex_sdk-0.3.0/tex_sdk.egg-info/top_level.txt +1 -0
tex_sdk-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tex-sdk
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Tex Python SDK for IntegrationBackend
|
|
5
|
+
Author: Tex
|
|
6
|
+
Requires-Python: >=3.9
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: httpx>=0.24.0
|
|
9
|
+
Provides-Extra: http2
|
|
10
|
+
Requires-Dist: h2>=4.1.0; extra == "http2"
|
|
11
|
+
|
|
12
|
+
# Tex SDK (Python)
|
|
13
|
+
|
|
14
|
+
Tex is a lightweight Python client for Tex’s IntegrationBackend HTTP API.
|
|
15
|
+
|
|
16
|
+
This README is intentionally implementation-grounded: it documents exactly what the SDK does today based on the code in:
|
|
17
|
+
|
|
18
|
+
- `tex/__init__.py` (public exports)
|
|
19
|
+
- `tex/config.py` (endpoint paths + config)
|
|
20
|
+
- `tex/client.py` (client behavior, auth, retries, request/response handling)
|
|
21
|
+
- `tex/errors.py` (error types)
|
|
22
|
+
|
|
23
|
+
If you are reading this inside the repo, those files are the source of truth.
|
|
24
|
+
|
|
25
|
+
## What you get
|
|
26
|
+
|
|
27
|
+
- One client class: `Tex`
|
|
28
|
+
- Three auth modes (API key, org+user login, or direct access token)
|
|
29
|
+
- Minimal, predictable return values: most methods return JSON as `dict`
|
|
30
|
+
- Friendly NLQ output via `AskResult`
|
|
31
|
+
- Typed package (`tex/py.typed`) for mypy/pyright
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
pip install tex-sdk
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Optional: enable HTTP/2 (requires `h2`):
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
pip install "tex-sdk[http2]"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Python requirement (from `pyproject.toml`): Python 3.9+
|
|
46
|
+
|
|
47
|
+
## Quick start
|
|
48
|
+
|
|
49
|
+
Important: the SDK’s `base_url` must point at IntegrationBackend (the FastAPI gateway), not HelixDB directly.
|
|
50
|
+
|
|
51
|
+
### 1) API key auth (recommended)
|
|
52
|
+
|
|
53
|
+
This is the simplest and most “production-like” flow.
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from tex import Tex
|
|
57
|
+
|
|
58
|
+
tx = Tex("http://localhost:8000", api_key="sk_live_...")
|
|
59
|
+
|
|
60
|
+
tx.store_memory(
|
|
61
|
+
"Hello from Tex SDK.",
|
|
62
|
+
type="document",
|
|
63
|
+
metadata={"source": "quickstart"},
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
answer = tx.ask("What did I just store?")
|
|
67
|
+
print(answer.text)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2) Org + user login (dev / testing)
|
|
71
|
+
|
|
72
|
+
If your IntegrationBackend allows `/auth/login` for a given org+user, you can use:
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from tex import Tex
|
|
76
|
+
|
|
77
|
+
tx = Tex(
|
|
78
|
+
"http://localhost:8000",
|
|
79
|
+
org_id="org_123",
|
|
80
|
+
user_id="user_456",
|
|
81
|
+
session_id="s1", # optional
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
tx.store_memory("I like espresso.", type="document")
|
|
85
|
+
print(tx.ask("What do I like?").text)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3) Direct access token (bring-your-own JWT)
|
|
89
|
+
|
|
90
|
+
If you already obtained an access token (e.g., out-of-band), pass it directly:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
from tex import Tex
|
|
94
|
+
|
|
95
|
+
tx = Tex("http://localhost:8000", access_token="eyJ...")
|
|
96
|
+
print(tx.whoami())
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## The public API surface
|
|
100
|
+
|
|
101
|
+
The package exports (see `tex/__init__.py`):
|
|
102
|
+
|
|
103
|
+
- `Tex`
|
|
104
|
+
- `AskResult`
|
|
105
|
+
- `TexConfig`, `TexEndpoints`
|
|
106
|
+
- `TexError`, `TexAuthError`, `TexHTTPError`
|
|
107
|
+
|
|
108
|
+
## Concepts
|
|
109
|
+
|
|
110
|
+
### Multi-tenant scope
|
|
111
|
+
|
|
112
|
+
IntegrationBackend is multi-tenant. Requests typically belong to a tenant:
|
|
113
|
+
|
|
114
|
+
- `org_id`
|
|
115
|
+
- `user_id`
|
|
116
|
+
- `session_id` (optional)
|
|
117
|
+
|
|
118
|
+
In the SDK, “scope” is included in ingestion payloads via `Tex._scope_payload()`. Internally:
|
|
119
|
+
|
|
120
|
+
- The backend ultimately trusts the JWT for tenant claims.
|
|
121
|
+
- The SDK still sends `scope` because the request models expect it.
|
|
122
|
+
- When using API keys, the SDK attempts to discover tenant claims by calling `GET /auth/verify`.
|
|
123
|
+
|
|
124
|
+
If tenant discovery fails, the SDK may send placeholders (`"_"`) for `org_id`/`user_id`; the backend is expected to ignore these and use the JWT tenant instead.
|
|
125
|
+
|
|
126
|
+
### Correlation IDs
|
|
127
|
+
|
|
128
|
+
Each request includes a unique `X-Correlation-ID` header generated per request (see `tex/client.py`).
|
|
129
|
+
|
|
130
|
+
- This is useful for tracing requests through your logs.
|
|
131
|
+
- When available, the SDK also exposes this as `request_id` on raised exceptions.
|
|
132
|
+
|
|
133
|
+
## Authentication: exact behavior
|
|
134
|
+
|
|
135
|
+
This section mirrors `Tex._ensure_auth()` and `Tex._refresh()` in `tex/client.py`.
|
|
136
|
+
|
|
137
|
+
### Auth modes (in priority order)
|
|
138
|
+
|
|
139
|
+
When the SDK needs auth, it chooses the first available:
|
|
140
|
+
|
|
141
|
+
1) If `access_token` is already present in memory → use it.
|
|
142
|
+
2) If `TexConfig.access_token` is provided → use it.
|
|
143
|
+
3) Else if `api_key` is provided:
|
|
144
|
+
- `POST /auth/token-exchange` with `{ "api_key": "..." }` → get `access_token` (+ optional `refresh_token`)
|
|
145
|
+
- `GET /auth/verify` to discover tenant claims
|
|
146
|
+
- If you also explicitly provided `user_id`, the SDK then calls `POST /auth/login` to mint a user-scoped token.
|
|
147
|
+
4) Else if `org_id` + `user_id` is provided:
|
|
148
|
+
- `POST /auth/login` with `{ org_id, user_id, session_id }`
|
|
149
|
+
5) Otherwise → raise `TexAuthError` (“No auth configured...”).
|
|
150
|
+
|
|
151
|
+
### Refresh + retry
|
|
152
|
+
|
|
153
|
+
All authenticated requests go through `_request()`.
|
|
154
|
+
|
|
155
|
+
- If a request returns HTTP 401:
|
|
156
|
+
- The SDK attempts `_refresh()` and retries the request once.
|
|
157
|
+
- Refresh rules:
|
|
158
|
+
- If `refresh_token` exists: `POST /auth/refresh`.
|
|
159
|
+
- Else if `api_key` exists: re-exchange the API key (and, if `user_id` was set, re-login).
|
|
160
|
+
- Otherwise: raise `TexAuthError`.
|
|
161
|
+
|
|
162
|
+
## Configuration
|
|
163
|
+
|
|
164
|
+
### `TexConfig`
|
|
165
|
+
|
|
166
|
+
You can construct the client with either:
|
|
167
|
+
|
|
168
|
+
- A `base_url` string: `Tex("http://localhost:8000", api_key=...)`
|
|
169
|
+
- A `TexConfig` object: `Tex(TexConfig(base_url=..., api_key=...))`
|
|
170
|
+
|
|
171
|
+
Fields (see `tex/config.py`):
|
|
172
|
+
|
|
173
|
+
- `base_url`: IntegrationBackend URL, e.g. `http://localhost:8000`
|
|
174
|
+
- Auth fields: `api_key`, `org_id`, `user_id`, `session_id`, `access_token`, `refresh_token`
|
|
175
|
+
- Transport: `timeout_s` (default 15s), `http2` (default True)
|
|
176
|
+
- `endpoints`: a `TexEndpoints` instance
|
|
177
|
+
|
|
178
|
+
### `TexEndpoints`
|
|
179
|
+
|
|
180
|
+
`TexEndpoints` contains the path strings the SDK calls (all relative to `base_url`).
|
|
181
|
+
|
|
182
|
+
Defaults (see `tex/config.py`):
|
|
183
|
+
|
|
184
|
+
- Auth:
|
|
185
|
+
- `auth_login`: `/auth/login`
|
|
186
|
+
- `auth_refresh`: `/auth/refresh`
|
|
187
|
+
- `auth_token_exchange`: `/auth/token-exchange`
|
|
188
|
+
- `auth_verify`: `/auth/verify`
|
|
189
|
+
- Ingestion:
|
|
190
|
+
- `ingestion_document`: `/ingestion/document`
|
|
191
|
+
- `ingestion_episode`: `/ingestion/episode`
|
|
192
|
+
- `ingestion_preference`: `/ingestion/preference`
|
|
193
|
+
- `ingestion_status`: `/ingestion/status/{job_id}`
|
|
194
|
+
- `ingestion_batch`: `/ingestion/batch`
|
|
195
|
+
- DB:
|
|
196
|
+
- `db_query`: `/helixdb/query`
|
|
197
|
+
- `db_schema`: `/helixdb/schema`
|
|
198
|
+
- NLQ: `nlq_execute`: `/nlq/execute`
|
|
199
|
+
- Search: `search`: `/search`
|
|
200
|
+
- Memories CRUD: `memories_list`, `memories_get`, `memories_delete`, `memories_update`
|
|
201
|
+
- Episodes: `episodes_list`: `/memories/episodes`
|
|
202
|
+
- Users: `user_profile`: `/users/profile`
|
|
203
|
+
|
|
204
|
+
If your deployment uses different routes/prefixes, override:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
from tex import Tex, TexConfig, TexEndpoints
|
|
208
|
+
|
|
209
|
+
endpoints = TexEndpoints(
|
|
210
|
+
# example override
|
|
211
|
+
db_query="/db/query",
|
|
212
|
+
db_schema="/db/schema",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
tx = Tex(TexConfig(base_url="https://api.example.com", api_key="sk_live_...", endpoints=endpoints))
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## API reference (method-by-method)
|
|
219
|
+
|
|
220
|
+
All examples assume:
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
from tex import Tex
|
|
224
|
+
|
|
225
|
+
tx = Tex("http://localhost:8000", api_key="sk_live_...")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### `Tex.store_memory(...)`
|
|
229
|
+
|
|
230
|
+
Single ingestion entrypoint. Behavior depends on `type`.
|
|
231
|
+
|
|
232
|
+
Signature (from `tex/client.py`):
|
|
233
|
+
|
|
234
|
+
- `content`: `str` or `list[dict]` (depending on `type`)
|
|
235
|
+
- `type`: one of `"document"`, `"episode"`, `"preference"`
|
|
236
|
+
- `format`: for documents, default `"text"`
|
|
237
|
+
- `metadata`: optional dict
|
|
238
|
+
- `options`: optional dict (passed through)
|
|
239
|
+
- `episode_id`: only for episode ingestion
|
|
240
|
+
|
|
241
|
+
#### Document ingestion (`type="document"`)
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
resp = tx.store_memory(
|
|
245
|
+
"A short document to ingest.",
|
|
246
|
+
type="document",
|
|
247
|
+
format="text",
|
|
248
|
+
metadata={"source": "docs"},
|
|
249
|
+
)
|
|
250
|
+
print(resp)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Notes:
|
|
254
|
+
|
|
255
|
+
- For `type="document"`, `content` must be a string or the SDK raises `ValueError`.
|
|
256
|
+
- The request payload includes `scope` derived from your auth.
|
|
257
|
+
|
|
258
|
+
#### Episode ingestion (`type="episode"`)
|
|
259
|
+
|
|
260
|
+
Episodes represent chat-like messages.
|
|
261
|
+
|
|
262
|
+
You can pass a single string (it becomes one `{"role":"user","content":...}` message):
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
resp = tx.store_memory(
|
|
266
|
+
"Today I called Alice and discussed the plan.",
|
|
267
|
+
type="episode",
|
|
268
|
+
)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Or pass a message list:
|
|
272
|
+
|
|
273
|
+
```python
|
|
274
|
+
messages = [
|
|
275
|
+
{"role": "user", "content": "Book a table for two."},
|
|
276
|
+
{"role": "assistant", "content": "Which restaurant?"},
|
|
277
|
+
{"role": "user", "content": "Somewhere near downtown."},
|
|
278
|
+
]
|
|
279
|
+
|
|
280
|
+
resp = tx.store_memory(messages, type="episode", episode_id="ep_001")
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
If you pass an invalid structure, the SDK raises `ValueError`.
|
|
284
|
+
|
|
285
|
+
#### Preference ingestion (`type="preference"`)
|
|
286
|
+
|
|
287
|
+
Preference ingestion expects preferences in `metadata["preferences"]`.
|
|
288
|
+
|
|
289
|
+
```python
|
|
290
|
+
resp = tx.store_memory(
|
|
291
|
+
"ignored",
|
|
292
|
+
type="preference",
|
|
293
|
+
metadata={
|
|
294
|
+
"preferences": [
|
|
295
|
+
{"key": "drink", "value": "espresso", "confidence": 0.9},
|
|
296
|
+
]
|
|
297
|
+
},
|
|
298
|
+
)
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Important:
|
|
302
|
+
|
|
303
|
+
- For `type="preference"`, the SDK ignores `content` and requires a non-empty list at `metadata["preferences"]`.
|
|
304
|
+
- The SDK sends `{ org_id, user_id, session_id, preferences }` using discovered scope.
|
|
305
|
+
|
|
306
|
+
### `Tex.job(job_id)`
|
|
307
|
+
|
|
308
|
+
Fetch background ingestion status.
|
|
309
|
+
|
|
310
|
+
```python
|
|
311
|
+
status = tx.job("job_...")
|
|
312
|
+
print(status)
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Internally calls `GET /ingestion/status/{job_id}`.
|
|
316
|
+
|
|
317
|
+
### `Tex.batch_store(documents)`
|
|
318
|
+
|
|
319
|
+
Ingest multiple documents in one call.
|
|
320
|
+
|
|
321
|
+
Each document dict should contain:
|
|
322
|
+
|
|
323
|
+
- `data` (str)
|
|
324
|
+
- optional `format`, `metadata`, `options`
|
|
325
|
+
|
|
326
|
+
```python
|
|
327
|
+
resp = tx.batch_store(
|
|
328
|
+
[
|
|
329
|
+
{"data": "doc 1", "metadata": {"source": "batch"}},
|
|
330
|
+
{"data": "doc 2", "format": "text"},
|
|
331
|
+
]
|
|
332
|
+
)
|
|
333
|
+
print(resp)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
Return shape depends on IntegrationBackend; the SDK returns the JSON response.
|
|
337
|
+
|
|
338
|
+
### `Tex.search(query, ...)`
|
|
339
|
+
|
|
340
|
+
Fast semantic search.
|
|
341
|
+
|
|
342
|
+
```python
|
|
343
|
+
resp = tx.search("espresso", top_k=5)
|
|
344
|
+
print(resp)
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Optional parameters:
|
|
348
|
+
|
|
349
|
+
- `min_score`: float
|
|
350
|
+
- `label`: string label filter
|
|
351
|
+
- `metadata_filter`: dict
|
|
352
|
+
|
|
353
|
+
### `Tex.ask(question, ...)` → `AskResult`
|
|
354
|
+
|
|
355
|
+
Natural language query execution.
|
|
356
|
+
|
|
357
|
+
```python
|
|
358
|
+
ans = tx.ask("What did I store recently?")
|
|
359
|
+
print(ans.text)
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Return type is `AskResult`:
|
|
363
|
+
|
|
364
|
+
- `text`: human-readable summary assembled from evidence/bindings/documents
|
|
365
|
+
- `evidence`: list of evidence strings
|
|
366
|
+
- `entities`: list of extracted/bound entity names
|
|
367
|
+
- `documents`: list of document identifiers
|
|
368
|
+
- `raw`: the full JSON response dict
|
|
369
|
+
|
|
370
|
+
Parameters (passed through to `/nlq/execute`):
|
|
371
|
+
|
|
372
|
+
- `execute` (default True)
|
|
373
|
+
- `enable_pruning` (default True)
|
|
374
|
+
- `use_local_intelligence` (default True)
|
|
375
|
+
- `intent_match_options` (optional dict)
|
|
376
|
+
- `compact` (default True)
|
|
377
|
+
|
|
378
|
+
### `Tex.query(query, params=None)`
|
|
379
|
+
|
|
380
|
+
Execute a DB query via IntegrationBackend.
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
resp = tx.query(
|
|
384
|
+
"MATCH (n) RETURN n LIMIT $limit",
|
|
385
|
+
params={"limit": 5},
|
|
386
|
+
)
|
|
387
|
+
print(resp)
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
This calls `POST /helixdb/query` by default (see `TexEndpoints.db_query`).
|
|
391
|
+
|
|
392
|
+
### `Tex.schema()`
|
|
393
|
+
|
|
394
|
+
Fetch the database schema.
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
schema = tx.schema()
|
|
398
|
+
print(schema)
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
This calls `GET /helixdb/schema` by default.
|
|
402
|
+
|
|
403
|
+
### `Tex.whoami()`
|
|
404
|
+
|
|
405
|
+
Returns the tenant context as seen by IntegrationBackend (great for debugging auth/scope).
|
|
406
|
+
|
|
407
|
+
```python
|
|
408
|
+
print(tx.whoami())
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Internally, it ensures auth, then calls `GET /auth/verify` (unless already cached).
|
|
412
|
+
|
|
413
|
+
### Memories CRUD
|
|
414
|
+
|
|
415
|
+
These map to `/memories` endpoints.
|
|
416
|
+
|
|
417
|
+
#### `Tex.get_memory(memory_id)`
|
|
418
|
+
|
|
419
|
+
```python
|
|
420
|
+
mem = tx.get_memory("mem_...")
|
|
421
|
+
print(mem)
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
#### `Tex.list_memories(type=None, limit=50, offset=0)`
|
|
425
|
+
|
|
426
|
+
```python
|
|
427
|
+
resp = tx.list_memories(limit=20, offset=0)
|
|
428
|
+
print(resp)
|
|
429
|
+
|
|
430
|
+
docs = tx.list_memories(type="document", limit=20)
|
|
431
|
+
print(docs)
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
#### `Tex.update_memory(memory_id, content=None, metadata=None, options=None)`
|
|
435
|
+
|
|
436
|
+
```python
|
|
437
|
+
resp = tx.update_memory(
|
|
438
|
+
"mem_...",
|
|
439
|
+
content="updated content",
|
|
440
|
+
metadata={"tag": "updated"},
|
|
441
|
+
)
|
|
442
|
+
print(resp)
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
#### `Tex.delete_memory(memory_id)`
|
|
446
|
+
|
|
447
|
+
```python
|
|
448
|
+
resp = tx.delete_memory("mem_...")
|
|
449
|
+
print(resp)
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
#### `Tex.delete_memories(user_id=None)`
|
|
453
|
+
|
|
454
|
+
Bulk delete.
|
|
455
|
+
|
|
456
|
+
- If `user_id` is omitted, deletes the caller’s memories.
|
|
457
|
+
- If `user_id` is provided and differs from caller, IntegrationBackend may require elevated roles.
|
|
458
|
+
|
|
459
|
+
```python
|
|
460
|
+
resp = tx.delete_memories()
|
|
461
|
+
print(resp)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Episodes
|
|
465
|
+
|
|
466
|
+
#### `Tex.list_episodes(limit=50, offset=0, since=None)`
|
|
467
|
+
|
|
468
|
+
```python
|
|
469
|
+
resp = tx.list_episodes(limit=10)
|
|
470
|
+
print(resp)
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### User profile
|
|
474
|
+
|
|
475
|
+
#### `Tex.get_profile(format="text")`
|
|
476
|
+
|
|
477
|
+
Returns a synthesized profile derived from preferences and episodic memories.
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
profile = tx.get_profile(format="text")
|
|
481
|
+
print(profile)
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
## Errors and exception handling
|
|
485
|
+
|
|
486
|
+
Errors are defined in `tex/errors.py`.
|
|
487
|
+
|
|
488
|
+
### `TexHTTPError`
|
|
489
|
+
|
|
490
|
+
Raised for non-auth HTTP failures (status >= 400 excluding 401/403) and network/timeout errors.
|
|
491
|
+
|
|
492
|
+
Fields:
|
|
493
|
+
|
|
494
|
+
- `message` (str)
|
|
495
|
+
- `status_code` (optional int)
|
|
496
|
+
- `request_id` (optional str; pulled from `X-Correlation-ID` response header if present)
|
|
497
|
+
- `response_text` (optional str; truncated to 2000 chars)
|
|
498
|
+
- `details` (any; parsed JSON when possible)
|
|
499
|
+
|
|
500
|
+
### `TexAuthError`
|
|
501
|
+
|
|
502
|
+
Subclass of `TexHTTPError`, raised for auth issues:
|
|
503
|
+
|
|
504
|
+
- “No auth configured”
|
|
505
|
+
- token exchange/login failures
|
|
506
|
+
- HTTP 401/403 responses
|
|
507
|
+
|
|
508
|
+
### Typical pattern
|
|
509
|
+
|
|
510
|
+
```python
|
|
511
|
+
from tex import Tex, TexAuthError, TexHTTPError
|
|
512
|
+
|
|
513
|
+
tx = Tex("http://localhost:8000", api_key="sk_live_...")
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
print(tx.whoami())
|
|
517
|
+
except TexAuthError as e:
|
|
518
|
+
# Wrong key, missing roles, expired/invalid refresh, etc.
|
|
519
|
+
print("auth failed", str(e), e.status_code)
|
|
520
|
+
except TexHTTPError as e:
|
|
521
|
+
# Non-auth HTTP errors (422, 500, network issues)
|
|
522
|
+
print("request failed", str(e), e.status_code)
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
## Request/response handling details
|
|
526
|
+
|
|
527
|
+
This mirrors `Tex._request()` and `_post_noauth()`.
|
|
528
|
+
|
|
529
|
+
- JSON responses are returned as Python dicts.
|
|
530
|
+
- If the response body is valid JSON but not a dict (e.g., a list), the SDK wraps it as `{ "data": <payload> }`.
|
|
531
|
+
- If the response is not JSON, the SDK returns `{ "data": "<text>" }`.
|
|
532
|
+
- For error responses, the SDK tries to extract `detail` or `message` from JSON. Otherwise: “Request failed”.
|
|
533
|
+
|
|
534
|
+
Special hint for HTTP 422:
|
|
535
|
+
|
|
536
|
+
- If the error mentions “IntentGraph validation” / “root variable must have a type”, the SDK appends a hint suggesting schema/planner issues.
|
|
537
|
+
|
|
538
|
+
## Running the smoketest script (repo)
|
|
539
|
+
|
|
540
|
+
The repo includes a minimal end-to-end tester:
|
|
541
|
+
|
|
542
|
+
- `tools/tex_sdk_smoketest.py`
|
|
543
|
+
|
|
544
|
+
It is designed to work:
|
|
545
|
+
|
|
546
|
+
- When installed from PyPI (`pip install tex-sdk`)
|
|
547
|
+
- When run directly from this repo (it falls back to adding the repo root to `sys.path`)
|
|
548
|
+
|
|
549
|
+
### Environment variables
|
|
550
|
+
|
|
551
|
+
- `TEX_BASE_URL` (default: `http://localhost:8000`)
|
|
552
|
+
- `TEX_TIMEOUT_S` (default: `30`)
|
|
553
|
+
|
|
554
|
+
Auth (set exactly one of the following groups):
|
|
555
|
+
|
|
556
|
+
1) API key:
|
|
557
|
+
|
|
558
|
+
- `TEX_API_KEY=sk_live_...`
|
|
559
|
+
|
|
560
|
+
2) Org+user login:
|
|
561
|
+
|
|
562
|
+
- `TEX_ORG_ID=...`
|
|
563
|
+
- `TEX_USER_ID=...`
|
|
564
|
+
- `TEX_SESSION_ID=...` (optional)
|
|
565
|
+
|
|
566
|
+
3) Direct token:
|
|
567
|
+
|
|
568
|
+
- `TEX_ACCESS_TOKEN=eyJ...`
|
|
569
|
+
|
|
570
|
+
### Run
|
|
571
|
+
|
|
572
|
+
```powershell
|
|
573
|
+
$env:TEX_BASE_URL = "http://localhost:8000"
|
|
574
|
+
$env:TEX_API_KEY = "sk_live_..."
|
|
575
|
+
|
|
576
|
+
python tools/tex_sdk_smoketest.py
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
The script performs:
|
|
580
|
+
|
|
581
|
+
- `GET /health` (unauth quick check)
|
|
582
|
+
- `whoami()`
|
|
583
|
+
- `store_memory(type="document")`
|
|
584
|
+
- polls `job(job_id)` (if job_id returned)
|
|
585
|
+
- `search(...)`
|
|
586
|
+
- `ask(...)`
|
|
587
|
+
|
|
588
|
+
## Migration notes (in-repo users)
|
|
589
|
+
|
|
590
|
+
Inside this repo, there is also an `sdk/` package that re-exports everything from `tex/` as a compatibility shim.
|
|
591
|
+
|
|
592
|
+
- New code should import from `tex`.
|
|
593
|
+
- Old code importing `sdk` will continue to work (see `sdk/__init__.py`).
|
|
594
|
+
|
|
595
|
+
## Troubleshooting
|
|
596
|
+
|
|
597
|
+
### “Connection refused” / health check fails
|
|
598
|
+
|
|
599
|
+
- Ensure IntegrationBackend is running (default: `http://localhost:8000/health`).
|
|
600
|
+
- The SDK does not start services; it only calls HTTP endpoints.
|
|
601
|
+
|
|
602
|
+
### HTTP 401 / 403
|
|
603
|
+
|
|
604
|
+
- Verify you’re using the correct auth mode.
|
|
605
|
+
- For API key auth: ensure the key is valid and belongs to the tenant you expect.
|
|
606
|
+
- For org+user login: ensure `/auth/login` is enabled for that org/user.
|
|
607
|
+
|
|
608
|
+
### HTTP 422 (validation errors)
|
|
609
|
+
|
|
610
|
+
- Usually means your request payload doesn’t match backend expectations.
|
|
611
|
+
- For NLQ/planner-related validation errors, confirm the DB schema is available and the planner can fetch it.
|
|
612
|
+
|
|
613
|
+
### HTTP/2 import error (`h2`)
|
|
614
|
+
|
|
615
|
+
If you see an ImportError mentioning `h2`, install:
|
|
616
|
+
|
|
617
|
+
```bash
|
|
618
|
+
pip install "tex-sdk[http2]"
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
Or disable HTTP/2:
|
|
622
|
+
|
|
623
|
+
```python
|
|
624
|
+
from tex import Tex
|
|
625
|
+
|
|
626
|
+
tx = Tex("http://localhost:8000", api_key="sk_live_...", http2=False)
|
|
627
|
+
```
|