woobe-sdk 0.1.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.
- woobe_sdk-0.1.0/LICENSE.md +21 -0
- woobe_sdk-0.1.0/PKG-INFO +322 -0
- woobe_sdk-0.1.0/README.md +300 -0
- woobe_sdk-0.1.0/pyproject.toml +52 -0
- woobe_sdk-0.1.0/setup.cfg +4 -0
- woobe_sdk-0.1.0/src/woobe/__init__.py +37 -0
- woobe_sdk-0.1.0/src/woobe/_transport/__init__.py +1 -0
- woobe_sdk-0.1.0/src/woobe/_transport/http.py +193 -0
- woobe_sdk-0.1.0/src/woobe/_transport/sse.py +160 -0
- woobe_sdk-0.1.0/src/woobe/chat.py +262 -0
- woobe_sdk-0.1.0/src/woobe/client.py +43 -0
- woobe_sdk-0.1.0/src/woobe/connect.py +21 -0
- woobe_sdk-0.1.0/src/woobe/contracts.py +175 -0
- woobe_sdk-0.1.0/src/woobe/errors.py +35 -0
- woobe_sdk-0.1.0/src/woobe/events.py +25 -0
- woobe_sdk-0.1.0/src/woobe/py.typed +0 -0
- woobe_sdk-0.1.0/src/woobe/results.py +209 -0
- woobe_sdk-0.1.0/src/woobe/targets.py +146 -0
- woobe_sdk-0.1.0/src/woobe_sdk.egg-info/PKG-INFO +322 -0
- woobe_sdk-0.1.0/src/woobe_sdk.egg-info/SOURCES.txt +26 -0
- woobe_sdk-0.1.0/src/woobe_sdk.egg-info/dependency_links.txt +1 -0
- woobe_sdk-0.1.0/src/woobe_sdk.egg-info/requires.txt +9 -0
- woobe_sdk-0.1.0/src/woobe_sdk.egg-info/top_level.txt +1 -0
- woobe_sdk-0.1.0/tests/test_chat.py +375 -0
- woobe_sdk-0.1.0/tests/test_contracts.py +101 -0
- woobe_sdk-0.1.0/tests/test_http.py +235 -0
- woobe_sdk-0.1.0/tests/test_public_api.py +159 -0
- woobe_sdk-0.1.0/tests/test_sse.py +90 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Woobe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
woobe_sdk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: woobe-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for the Woobe Runtime API
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/A1b3rt0M3rcad0/woobe-sdk
|
|
7
|
+
Project-URL: Repository, https://github.com/A1b3rt0M3rcad0/woobe-sdk
|
|
8
|
+
Project-URL: Issues, https://github.com/A1b3rt0M3rcad0/woobe-sdk/issues
|
|
9
|
+
Keywords: woobe,sdk,agents,agentic,runtime
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE.md
|
|
13
|
+
Requires-Dist: httpx<1,>=0.28
|
|
14
|
+
Requires-Dist: pydantic<3,>=2.10
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: build<2,>=1.2; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest<9,>=8.3; extra == "dev"
|
|
18
|
+
Requires-Dist: pytest-asyncio<1,>=0.24; extra == "dev"
|
|
19
|
+
Requires-Dist: ruff<1,>=0.11; extra == "dev"
|
|
20
|
+
Requires-Dist: twine<7,>=6; extra == "dev"
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# Woobe SDK
|
|
24
|
+
|
|
25
|
+
Python SDK for consuming Agents and Agent Networks running on the Woobe Runtime API.
|
|
26
|
+
|
|
27
|
+
The SDK is intentionally a **runtime client**, not a second control plane and not an agent framework. Agent configuration, Releases, Tools, Knowledge, execution strategies and runtime infrastructure remain server-side in Woobe. Applications connect to an already published runtime target and consume its execution events.
|
|
28
|
+
|
|
29
|
+
> **Pre-1.0:** the SDK is public and usable, but its APIs can still evolve before the 1.0.0 compatibility boundary.
|
|
30
|
+
|
|
31
|
+
## Quick start
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
from woobe import Woobe
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
woobe = Woobe()
|
|
38
|
+
|
|
39
|
+
agent = woobe.connect.agent(
|
|
40
|
+
alias="support",
|
|
41
|
+
key="...",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
# No HTTP request is executed here.
|
|
45
|
+
chat = agent.chat(input="Olá")
|
|
46
|
+
|
|
47
|
+
# The Runtime request starts when events() is actually iterated.
|
|
48
|
+
async for event in chat.events():
|
|
49
|
+
print(event.type, event.payload)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`Chat` is lazy: constructing it does not create a Run. Iterating `events()` performs the Runtime request.
|
|
53
|
+
|
|
54
|
+
When the target policy supports conversational continuity, reuse the Session returned by the previous interaction:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
chat = agent.chat(
|
|
58
|
+
input="Continue de onde paramos",
|
|
59
|
+
session_id=previous_chat.session_id,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
async for event in chat.events():
|
|
63
|
+
print(event)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When the selected Release declares an External Context contract, provide its values on the Run:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
chat = agent.chat(
|
|
70
|
+
input="Consulte meus pedidos",
|
|
71
|
+
external_context={
|
|
72
|
+
"customer_id": "customer-123",
|
|
73
|
+
"language": "pt-BR",
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
async for event in chat.events():
|
|
78
|
+
print(event)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
The Runtime validates `external_context` against the Agent or Network Release contract during Acceptance. The SDK sends it only when creating the Run; reattach observes the already accepted Run and does not resend context.
|
|
82
|
+
|
|
83
|
+
After a completed Run, `Chat.result` exposes the terminal Runtime payload as typed SDK objects:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
chat = agent.chat(
|
|
87
|
+
input="O que perguntei antes?",
|
|
88
|
+
session_id=session_id,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
async for event in chat.events():
|
|
92
|
+
if event.type == "token":
|
|
93
|
+
print(event.payload["content"], end="")
|
|
94
|
+
|
|
95
|
+
result = chat.result
|
|
96
|
+
if result is not None:
|
|
97
|
+
print(result.answer)
|
|
98
|
+
print(result.message_id)
|
|
99
|
+
print(result.model)
|
|
100
|
+
print(result.provider)
|
|
101
|
+
|
|
102
|
+
if result.usage is not None:
|
|
103
|
+
print(result.usage.total_tokens)
|
|
104
|
+
print(result.usage.cost_usd)
|
|
105
|
+
|
|
106
|
+
print(result.agent_release_version)
|
|
107
|
+
print(result.execution_strategy)
|
|
108
|
+
|
|
109
|
+
if result.diagnostics is not None:
|
|
110
|
+
print(result.diagnostics.agent_runtime_latency_ms)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`result` is `None` before completion and for terminal failures that do not produce a completed result. Agent `done` and Network `execution_completed` events are normalized to the same `ChatResult` surface. Raw streaming events remain available unchanged through `events()`.
|
|
114
|
+
|
|
115
|
+
The typed result includes `Usage`, `Source`, `ToolCall`, `FallbackInfo`, `ExecutionEvent` and `ExecutionDiagnostics` objects. Unknown future Runtime fields are preserved so the SDK remains forward compatible with additive payload changes.
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
## Runtime contract validation
|
|
119
|
+
|
|
120
|
+
The SDK can validate both client-side contract declarations against the immutable Release bound to the Runtime Key before starting a Run.
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
from pydantic import BaseModel
|
|
124
|
+
|
|
125
|
+
from woobe import Woobe
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class SupportOutput(BaseModel):
|
|
129
|
+
message: str
|
|
130
|
+
confidence: float
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class SupportContext(BaseModel):
|
|
134
|
+
name: str
|
|
135
|
+
age: int
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
woobe = Woobe()
|
|
139
|
+
|
|
140
|
+
agent = woobe.connect.agent(
|
|
141
|
+
alias="support",
|
|
142
|
+
key="...",
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
validation = await agent.validate_contracts(
|
|
146
|
+
output_contract=SupportOutput,
|
|
147
|
+
external_context=SupportContext,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if not validation.valid:
|
|
151
|
+
for issue in validation.output_contract.issues:
|
|
152
|
+
print("output:", issue.code, issue.field)
|
|
153
|
+
for issue in validation.external_context.issues:
|
|
154
|
+
print("external_context:", issue.code, issue.field)
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`validate_contracts(...)` always declares both public contracts. Each argument accepts a Pydantic `BaseModel` type, a model instance, an explicit JSON Schema `dict`, or `None`. Local Pydantic `$ref` definitions are inlined before the request is sent to `POST /v1/contracts/validate`.
|
|
158
|
+
|
|
159
|
+
The Runtime Key determines the Agent or Network and the published `staging` or `production` Release being checked. The result contains the Release identity plus separate normalized comparisons for `output_contract` and `external_context`, including deterministic hashes and field-level mismatch issues.
|
|
160
|
+
|
|
161
|
+
For focused checks, the same target also exposes:
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
output = await agent.validate_output_contract(SupportOutput)
|
|
165
|
+
external = await agent.validate_external_context(SupportContext)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The older `validate_output_context(...)` name remains available as a backward-compatible alias for `validate_output_contract(...)`.
|
|
169
|
+
|
|
170
|
+
These calls validate the SDK's declared contract shape against the published Release. Runtime External Context values passed to `chat(external_context=...)` are still validated authoritatively during Run Acceptance, including required/default/session semantics.
|
|
171
|
+
|
|
172
|
+
Validation is explicit and separate from `chat()`; the SDK does not add a hidden contract request to every Run.
|
|
173
|
+
|
|
174
|
+
The same validation surface is available for Networks.
|
|
175
|
+
|
|
176
|
+
## Runtime model
|
|
177
|
+
|
|
178
|
+
The public SDK surface follows four concepts:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
Target -> Agent or Network
|
|
182
|
+
Session -> longitudinal/correlation boundary
|
|
183
|
+
Run -> one finite logical execution
|
|
184
|
+
Event -> one semantic event from that Run
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Every accepted Run belongs to a Session. This also applies to stateless Agent execution: when no Session is supplied, Woobe creates an isolated Session for identity and correlation. That does not enable implicit history continuity for a stateless Agent.
|
|
188
|
+
|
|
189
|
+
Every event yielded by `Chat.events()` is a canonical Runtime Protocol v2 `WoobeEvent`:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
async for event in chat.events():
|
|
193
|
+
event.protocol_version # 2
|
|
194
|
+
event.event_id
|
|
195
|
+
event.run_id
|
|
196
|
+
event.session_id
|
|
197
|
+
event.run_kind # "AGENT" | "NETWORK"
|
|
198
|
+
event.sequence
|
|
199
|
+
event.type
|
|
200
|
+
event.occurred_at
|
|
201
|
+
event.payload
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
The SDK preserves Woobe event names and payloads. It does not infer identity from payload aliases such as `execution_id` or `network_session_id`.
|
|
205
|
+
|
|
206
|
+
## `WoobeEvent`
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
class WoobeEvent(BaseModel):
|
|
210
|
+
protocol_version: Literal[2]
|
|
211
|
+
event_id: str
|
|
212
|
+
run_id: str
|
|
213
|
+
session_id: str
|
|
214
|
+
run_kind: Literal["AGENT", "NETWORK"]
|
|
215
|
+
sequence: int
|
|
216
|
+
type: str
|
|
217
|
+
occurred_at: datetime
|
|
218
|
+
payload: dict[str, Any]
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
All fields above are mandatory for semantic events. The SDK validates the Runtime v2 envelope instead of fabricating missing identity or ordering metadata.
|
|
222
|
+
|
|
223
|
+
Transport/control frames are different. Heartbeats, realtime-degradation notices and pre-Acceptance errors do not pretend to be semantic Run events. They are handled internally by the SDK and are not yielded as `WoobeEvent` objects.
|
|
224
|
+
|
|
225
|
+
## Reattach and duplicate-Run safety
|
|
226
|
+
|
|
227
|
+
A stream connection observes a Run; it does not own it.
|
|
228
|
+
|
|
229
|
+
Once the canonical `run_id` is known, a transport interruption is recovered through the reattach endpoint for the **same Run**:
|
|
230
|
+
|
|
231
|
+
```text
|
|
232
|
+
POST /v1/run/stream
|
|
233
|
+
|
|
|
234
|
+
v
|
|
235
|
+
canonical run_id + session_id
|
|
236
|
+
|
|
|
237
|
+
connection loss
|
|
238
|
+
|
|
|
239
|
+
v
|
|
240
|
+
GET /v1/runs/{run_id}/stream
|
|
241
|
+
|
|
|
242
|
+
v
|
|
243
|
+
run.state @ high watermark
|
|
244
|
+
|
|
|
245
|
+
v
|
|
246
|
+
same Run
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
If only the Session is known, the SDK can resolve the active Run through `/v1/sessions/{session_id}/active-run` before reattaching.
|
|
250
|
+
|
|
251
|
+
The SDK never submits a second Agent execution after learning the canonical Run ID. If the initial Agent connection is lost before identity can be recovered safely, it fails closed rather than risking a duplicate Run. Network create retries reuse one idempotency key for the same logical execution.
|
|
252
|
+
|
|
253
|
+
## Sequence handling
|
|
254
|
+
|
|
255
|
+
`sequence` is the semantic ordering contract. The SSE `id:` field is a transport cursor and, when present for a semantic event, must match the canonical sequence.
|
|
256
|
+
|
|
257
|
+
The SDK:
|
|
258
|
+
|
|
259
|
+
- ignores stale or duplicate incremental events at or below the local sequence;
|
|
260
|
+
- detects sequence gaps and reattaches instead of guessing;
|
|
261
|
+
- treats `run.state` as replacement state at its high watermark;
|
|
262
|
+
- validates that Run, Session and Run kind do not change inside one `Chat`.
|
|
263
|
+
|
|
264
|
+
## Configuration
|
|
265
|
+
|
|
266
|
+
The default hosted endpoint is `https://api.woobe.com.br`. Self-hosted environments can provide a base URL explicitly or through `WOOBE_BASE_URL`:
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
woobe = Woobe(base_url="https://woobe.internal.example")
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
For long-lived processes, close the underlying async HTTP client on shutdown:
|
|
273
|
+
|
|
274
|
+
```python
|
|
275
|
+
await woobe.aclose()
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
or use an async context manager:
|
|
279
|
+
|
|
280
|
+
```python
|
|
281
|
+
async with Woobe() as woobe:
|
|
282
|
+
agent = woobe.connect.agent(alias="support", key="...")
|
|
283
|
+
async for event in agent.chat(input="Olá").events():
|
|
284
|
+
print(event)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Repository
|
|
288
|
+
|
|
289
|
+
```text
|
|
290
|
+
src/woobe/ public SDK and private transport implementation
|
|
291
|
+
tests/ SDK unit tests
|
|
292
|
+
examples/ small executable usage examples
|
|
293
|
+
docs/ architecture and runtime contract documentation
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Start with [`docs/README.md`](docs/README.md) for the documentation index.
|
|
297
|
+
|
|
298
|
+
## Development
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
python -m venv .venv
|
|
302
|
+
source .venv/bin/activate
|
|
303
|
+
pip install -e '.[dev]'
|
|
304
|
+
pytest
|
|
305
|
+
ruff check .
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
Pull requests run the same quality gate on supported Python versions. The integration branch is `master`.
|
|
309
|
+
|
|
310
|
+
## Governance and licensing
|
|
311
|
+
|
|
312
|
+
Woobe SDK is licensed under the [MIT License](LICENSE.md). The SDK license is independent from the licenses that govern the Woobe platform itself; using this client does not relicense Woobe Core or Enterprise software.
|
|
313
|
+
|
|
314
|
+
Repository policies:
|
|
315
|
+
|
|
316
|
+
- [Contributing](CONTRIBUTING.md)
|
|
317
|
+
- [Releasing](RELEASING.md)
|
|
318
|
+
- [Security](SECURITY.md)
|
|
319
|
+
- [Licensing](LICENSING.md)
|
|
320
|
+
- [Trademark policy](TRADEMARKS.md)
|
|
321
|
+
- [Changelog](CHANGELOG.md)
|
|
322
|
+
- [Coding-agent entry point](AGENTS.md)
|
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# Woobe SDK
|
|
2
|
+
|
|
3
|
+
Python SDK for consuming Agents and Agent Networks running on the Woobe Runtime API.
|
|
4
|
+
|
|
5
|
+
The SDK is intentionally a **runtime client**, not a second control plane and not an agent framework. Agent configuration, Releases, Tools, Knowledge, execution strategies and runtime infrastructure remain server-side in Woobe. Applications connect to an already published runtime target and consume its execution events.
|
|
6
|
+
|
|
7
|
+
> **Pre-1.0:** the SDK is public and usable, but its APIs can still evolve before the 1.0.0 compatibility boundary.
|
|
8
|
+
|
|
9
|
+
## Quick start
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from woobe import Woobe
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
woobe = Woobe()
|
|
16
|
+
|
|
17
|
+
agent = woobe.connect.agent(
|
|
18
|
+
alias="support",
|
|
19
|
+
key="...",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# No HTTP request is executed here.
|
|
23
|
+
chat = agent.chat(input="Olá")
|
|
24
|
+
|
|
25
|
+
# The Runtime request starts when events() is actually iterated.
|
|
26
|
+
async for event in chat.events():
|
|
27
|
+
print(event.type, event.payload)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`Chat` is lazy: constructing it does not create a Run. Iterating `events()` performs the Runtime request.
|
|
31
|
+
|
|
32
|
+
When the target policy supports conversational continuity, reuse the Session returned by the previous interaction:
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
chat = agent.chat(
|
|
36
|
+
input="Continue de onde paramos",
|
|
37
|
+
session_id=previous_chat.session_id,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
async for event in chat.events():
|
|
41
|
+
print(event)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
When the selected Release declares an External Context contract, provide its values on the Run:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
chat = agent.chat(
|
|
48
|
+
input="Consulte meus pedidos",
|
|
49
|
+
external_context={
|
|
50
|
+
"customer_id": "customer-123",
|
|
51
|
+
"language": "pt-BR",
|
|
52
|
+
},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
async for event in chat.events():
|
|
56
|
+
print(event)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
The Runtime validates `external_context` against the Agent or Network Release contract during Acceptance. The SDK sends it only when creating the Run; reattach observes the already accepted Run and does not resend context.
|
|
60
|
+
|
|
61
|
+
After a completed Run, `Chat.result` exposes the terminal Runtime payload as typed SDK objects:
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
chat = agent.chat(
|
|
65
|
+
input="O que perguntei antes?",
|
|
66
|
+
session_id=session_id,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
async for event in chat.events():
|
|
70
|
+
if event.type == "token":
|
|
71
|
+
print(event.payload["content"], end="")
|
|
72
|
+
|
|
73
|
+
result = chat.result
|
|
74
|
+
if result is not None:
|
|
75
|
+
print(result.answer)
|
|
76
|
+
print(result.message_id)
|
|
77
|
+
print(result.model)
|
|
78
|
+
print(result.provider)
|
|
79
|
+
|
|
80
|
+
if result.usage is not None:
|
|
81
|
+
print(result.usage.total_tokens)
|
|
82
|
+
print(result.usage.cost_usd)
|
|
83
|
+
|
|
84
|
+
print(result.agent_release_version)
|
|
85
|
+
print(result.execution_strategy)
|
|
86
|
+
|
|
87
|
+
if result.diagnostics is not None:
|
|
88
|
+
print(result.diagnostics.agent_runtime_latency_ms)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`result` is `None` before completion and for terminal failures that do not produce a completed result. Agent `done` and Network `execution_completed` events are normalized to the same `ChatResult` surface. Raw streaming events remain available unchanged through `events()`.
|
|
92
|
+
|
|
93
|
+
The typed result includes `Usage`, `Source`, `ToolCall`, `FallbackInfo`, `ExecutionEvent` and `ExecutionDiagnostics` objects. Unknown future Runtime fields are preserved so the SDK remains forward compatible with additive payload changes.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## Runtime contract validation
|
|
97
|
+
|
|
98
|
+
The SDK can validate both client-side contract declarations against the immutable Release bound to the Runtime Key before starting a Run.
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from pydantic import BaseModel
|
|
102
|
+
|
|
103
|
+
from woobe import Woobe
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SupportOutput(BaseModel):
|
|
107
|
+
message: str
|
|
108
|
+
confidence: float
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class SupportContext(BaseModel):
|
|
112
|
+
name: str
|
|
113
|
+
age: int
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
woobe = Woobe()
|
|
117
|
+
|
|
118
|
+
agent = woobe.connect.agent(
|
|
119
|
+
alias="support",
|
|
120
|
+
key="...",
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
validation = await agent.validate_contracts(
|
|
124
|
+
output_contract=SupportOutput,
|
|
125
|
+
external_context=SupportContext,
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
if not validation.valid:
|
|
129
|
+
for issue in validation.output_contract.issues:
|
|
130
|
+
print("output:", issue.code, issue.field)
|
|
131
|
+
for issue in validation.external_context.issues:
|
|
132
|
+
print("external_context:", issue.code, issue.field)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`validate_contracts(...)` always declares both public contracts. Each argument accepts a Pydantic `BaseModel` type, a model instance, an explicit JSON Schema `dict`, or `None`. Local Pydantic `$ref` definitions are inlined before the request is sent to `POST /v1/contracts/validate`.
|
|
136
|
+
|
|
137
|
+
The Runtime Key determines the Agent or Network and the published `staging` or `production` Release being checked. The result contains the Release identity plus separate normalized comparisons for `output_contract` and `external_context`, including deterministic hashes and field-level mismatch issues.
|
|
138
|
+
|
|
139
|
+
For focused checks, the same target also exposes:
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
output = await agent.validate_output_contract(SupportOutput)
|
|
143
|
+
external = await agent.validate_external_context(SupportContext)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
The older `validate_output_context(...)` name remains available as a backward-compatible alias for `validate_output_contract(...)`.
|
|
147
|
+
|
|
148
|
+
These calls validate the SDK's declared contract shape against the published Release. Runtime External Context values passed to `chat(external_context=...)` are still validated authoritatively during Run Acceptance, including required/default/session semantics.
|
|
149
|
+
|
|
150
|
+
Validation is explicit and separate from `chat()`; the SDK does not add a hidden contract request to every Run.
|
|
151
|
+
|
|
152
|
+
The same validation surface is available for Networks.
|
|
153
|
+
|
|
154
|
+
## Runtime model
|
|
155
|
+
|
|
156
|
+
The public SDK surface follows four concepts:
|
|
157
|
+
|
|
158
|
+
```text
|
|
159
|
+
Target -> Agent or Network
|
|
160
|
+
Session -> longitudinal/correlation boundary
|
|
161
|
+
Run -> one finite logical execution
|
|
162
|
+
Event -> one semantic event from that Run
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Every accepted Run belongs to a Session. This also applies to stateless Agent execution: when no Session is supplied, Woobe creates an isolated Session for identity and correlation. That does not enable implicit history continuity for a stateless Agent.
|
|
166
|
+
|
|
167
|
+
Every event yielded by `Chat.events()` is a canonical Runtime Protocol v2 `WoobeEvent`:
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
async for event in chat.events():
|
|
171
|
+
event.protocol_version # 2
|
|
172
|
+
event.event_id
|
|
173
|
+
event.run_id
|
|
174
|
+
event.session_id
|
|
175
|
+
event.run_kind # "AGENT" | "NETWORK"
|
|
176
|
+
event.sequence
|
|
177
|
+
event.type
|
|
178
|
+
event.occurred_at
|
|
179
|
+
event.payload
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The SDK preserves Woobe event names and payloads. It does not infer identity from payload aliases such as `execution_id` or `network_session_id`.
|
|
183
|
+
|
|
184
|
+
## `WoobeEvent`
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
class WoobeEvent(BaseModel):
|
|
188
|
+
protocol_version: Literal[2]
|
|
189
|
+
event_id: str
|
|
190
|
+
run_id: str
|
|
191
|
+
session_id: str
|
|
192
|
+
run_kind: Literal["AGENT", "NETWORK"]
|
|
193
|
+
sequence: int
|
|
194
|
+
type: str
|
|
195
|
+
occurred_at: datetime
|
|
196
|
+
payload: dict[str, Any]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
All fields above are mandatory for semantic events. The SDK validates the Runtime v2 envelope instead of fabricating missing identity or ordering metadata.
|
|
200
|
+
|
|
201
|
+
Transport/control frames are different. Heartbeats, realtime-degradation notices and pre-Acceptance errors do not pretend to be semantic Run events. They are handled internally by the SDK and are not yielded as `WoobeEvent` objects.
|
|
202
|
+
|
|
203
|
+
## Reattach and duplicate-Run safety
|
|
204
|
+
|
|
205
|
+
A stream connection observes a Run; it does not own it.
|
|
206
|
+
|
|
207
|
+
Once the canonical `run_id` is known, a transport interruption is recovered through the reattach endpoint for the **same Run**:
|
|
208
|
+
|
|
209
|
+
```text
|
|
210
|
+
POST /v1/run/stream
|
|
211
|
+
|
|
|
212
|
+
v
|
|
213
|
+
canonical run_id + session_id
|
|
214
|
+
|
|
|
215
|
+
connection loss
|
|
216
|
+
|
|
|
217
|
+
v
|
|
218
|
+
GET /v1/runs/{run_id}/stream
|
|
219
|
+
|
|
|
220
|
+
v
|
|
221
|
+
run.state @ high watermark
|
|
222
|
+
|
|
|
223
|
+
v
|
|
224
|
+
same Run
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
If only the Session is known, the SDK can resolve the active Run through `/v1/sessions/{session_id}/active-run` before reattaching.
|
|
228
|
+
|
|
229
|
+
The SDK never submits a second Agent execution after learning the canonical Run ID. If the initial Agent connection is lost before identity can be recovered safely, it fails closed rather than risking a duplicate Run. Network create retries reuse one idempotency key for the same logical execution.
|
|
230
|
+
|
|
231
|
+
## Sequence handling
|
|
232
|
+
|
|
233
|
+
`sequence` is the semantic ordering contract. The SSE `id:` field is a transport cursor and, when present for a semantic event, must match the canonical sequence.
|
|
234
|
+
|
|
235
|
+
The SDK:
|
|
236
|
+
|
|
237
|
+
- ignores stale or duplicate incremental events at or below the local sequence;
|
|
238
|
+
- detects sequence gaps and reattaches instead of guessing;
|
|
239
|
+
- treats `run.state` as replacement state at its high watermark;
|
|
240
|
+
- validates that Run, Session and Run kind do not change inside one `Chat`.
|
|
241
|
+
|
|
242
|
+
## Configuration
|
|
243
|
+
|
|
244
|
+
The default hosted endpoint is `https://api.woobe.com.br`. Self-hosted environments can provide a base URL explicitly or through `WOOBE_BASE_URL`:
|
|
245
|
+
|
|
246
|
+
```python
|
|
247
|
+
woobe = Woobe(base_url="https://woobe.internal.example")
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
For long-lived processes, close the underlying async HTTP client on shutdown:
|
|
251
|
+
|
|
252
|
+
```python
|
|
253
|
+
await woobe.aclose()
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
or use an async context manager:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
async with Woobe() as woobe:
|
|
260
|
+
agent = woobe.connect.agent(alias="support", key="...")
|
|
261
|
+
async for event in agent.chat(input="Olá").events():
|
|
262
|
+
print(event)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Repository
|
|
266
|
+
|
|
267
|
+
```text
|
|
268
|
+
src/woobe/ public SDK and private transport implementation
|
|
269
|
+
tests/ SDK unit tests
|
|
270
|
+
examples/ small executable usage examples
|
|
271
|
+
docs/ architecture and runtime contract documentation
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Start with [`docs/README.md`](docs/README.md) for the documentation index.
|
|
275
|
+
|
|
276
|
+
## Development
|
|
277
|
+
|
|
278
|
+
```bash
|
|
279
|
+
python -m venv .venv
|
|
280
|
+
source .venv/bin/activate
|
|
281
|
+
pip install -e '.[dev]'
|
|
282
|
+
pytest
|
|
283
|
+
ruff check .
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Pull requests run the same quality gate on supported Python versions. The integration branch is `master`.
|
|
287
|
+
|
|
288
|
+
## Governance and licensing
|
|
289
|
+
|
|
290
|
+
Woobe SDK is licensed under the [MIT License](LICENSE.md). The SDK license is independent from the licenses that govern the Woobe platform itself; using this client does not relicense Woobe Core or Enterprise software.
|
|
291
|
+
|
|
292
|
+
Repository policies:
|
|
293
|
+
|
|
294
|
+
- [Contributing](CONTRIBUTING.md)
|
|
295
|
+
- [Releasing](RELEASING.md)
|
|
296
|
+
- [Security](SECURITY.md)
|
|
297
|
+
- [Licensing](LICENSING.md)
|
|
298
|
+
- [Trademark policy](TRADEMARKS.md)
|
|
299
|
+
- [Changelog](CHANGELOG.md)
|
|
300
|
+
- [Coding-agent entry point](AGENTS.md)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "woobe-sdk"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python SDK for the Woobe Runtime API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE.md"]
|
|
13
|
+
keywords = ["woobe", "sdk", "agents", "agentic", "runtime"]
|
|
14
|
+
dependencies = [
|
|
15
|
+
"httpx>=0.28,<1",
|
|
16
|
+
"pydantic>=2.10,<3",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.urls]
|
|
20
|
+
Homepage = "https://github.com/A1b3rt0M3rcad0/woobe-sdk"
|
|
21
|
+
Repository = "https://github.com/A1b3rt0M3rcad0/woobe-sdk"
|
|
22
|
+
Issues = "https://github.com/A1b3rt0M3rcad0/woobe-sdk/issues"
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = [
|
|
26
|
+
"build>=1.2,<2",
|
|
27
|
+
"pytest>=8.3,<9",
|
|
28
|
+
"pytest-asyncio>=0.24,<1",
|
|
29
|
+
"ruff>=0.11,<1",
|
|
30
|
+
"twine>=6,<7",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[tool.setuptools]
|
|
34
|
+
package-dir = {"" = "src"}
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
include = ["woobe*"]
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.package-data]
|
|
41
|
+
woobe = ["py.typed"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
|
45
|
+
testpaths = ["tests"]
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
target-version = "py311"
|
|
49
|
+
line-length = 100
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "UP", "B"]
|