axio-responses 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.
- axio_responses-0.1.0/.gitignore +14 -0
- axio_responses-0.1.0/PKG-INFO +106 -0
- axio_responses-0.1.0/README.md +92 -0
- axio_responses-0.1.0/pyproject.toml +47 -0
- axio_responses-0.1.0/src/axio_responses/__init__.py +67 -0
- axio_responses-0.1.0/src/axio_responses/py.typed +0 -0
- axio_responses-0.1.0/src/axio_responses/reader.py +460 -0
- axio_responses-0.1.0/src/axio_responses/request.py +206 -0
- axio_responses-0.1.0/tests/test_responses.py +888 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: axio-responses
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: The OpenAI Responses API as axio speaks it: request items in, StreamEvents out
|
|
5
|
+
Project-URL: Documentation, https://docs.axio-agent.com
|
|
6
|
+
Project-URL: Homepage, https://github.com/mosquito/axio-agent
|
|
7
|
+
Project-URL: Repository, https://github.com/mosquito/axio-agent
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: agent,llm,openai,responses,streaming
|
|
10
|
+
Requires-Python: >=3.12
|
|
11
|
+
Requires-Dist: axio
|
|
12
|
+
Requires-Dist: axio-sse
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# axio-responses
|
|
16
|
+
|
|
17
|
+
[](https://pypi.org/project/axio-responses/)
|
|
18
|
+
[](https://pypi.org/project/axio-responses/)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
|
|
21
|
+
The OpenAI Responses API as [axio](https://github.com/mosquito/axio-agent) speaks it: request items
|
|
22
|
+
in, `StreamEvent`s out.
|
|
23
|
+
|
|
24
|
+
Both halves live here rather than in a transport because two transports speak this API — the public
|
|
25
|
+
`/v1/responses` endpoint and the ChatGPT backend Codex uses. It knows nothing about HTTP and opens
|
|
26
|
+
no connection.
|
|
27
|
+
|
|
28
|
+
## Installation
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install axio-responses
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Building the request
|
|
37
|
+
|
|
38
|
+
<!-- name: test_readme_building_a_request -->
|
|
39
|
+
```python
|
|
40
|
+
from axio.blocks import TextBlock
|
|
41
|
+
from axio.messages import Message
|
|
42
|
+
from axio_responses import convert_messages, convert_tools
|
|
43
|
+
|
|
44
|
+
messages, system, tools = [Message(role="user", content=[TextBlock(text="hi")])], "be brief", []
|
|
45
|
+
|
|
46
|
+
instructions, items = convert_messages(messages, system)
|
|
47
|
+
payload = {
|
|
48
|
+
"model": "gpt-5.6",
|
|
49
|
+
"instructions": instructions,
|
|
50
|
+
"input": items,
|
|
51
|
+
"stream": True,
|
|
52
|
+
"tools": convert_tools(tools),
|
|
53
|
+
}
|
|
54
|
+
assert payload["instructions"] == "be brief"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`convert_messages` returns the system prompt separately, because this API takes it as
|
|
58
|
+
`instructions` rather than as a message. Tool calls and their outputs become `function_call` and
|
|
59
|
+
`function_call_output` items beside the messages, not blocks inside them.
|
|
60
|
+
|
|
61
|
+
### Reading the stream
|
|
62
|
+
|
|
63
|
+
`Responses` is an `axio_sse.Reader`: one `@on(...)` method per event, dispatching on the payload's
|
|
64
|
+
own `type`. Its class body names only the events it interprets. The API publishes one event family
|
|
65
|
+
per tool it can run, so that set grows with the tools and not with the protocol; everything else is
|
|
66
|
+
forwarded through `unmatched()` rather than dropped.
|
|
67
|
+
|
|
68
|
+
<!-- name: test_readme_reading_the_stream -->
|
|
69
|
+
```python
|
|
70
|
+
from collections.abc import AsyncIterator
|
|
71
|
+
|
|
72
|
+
import aiohttp
|
|
73
|
+
from axio.events import StreamEvent
|
|
74
|
+
from axio_responses import Responses
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
async def stream(resp: aiohttp.ClientResponse) -> AsyncIterator[StreamEvent]:
|
|
78
|
+
turn = Responses()
|
|
79
|
+
async for made in turn.over(resp.content.iter_any(), until="[DONE]"):
|
|
80
|
+
yield made
|
|
81
|
+
yield turn.finished()
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Events axio has no type for — the API's own hosted tools, its audio, its bookkeeping — travel as
|
|
85
|
+
`ProviderEvent` under the provider's own name rather than being dropped.
|
|
86
|
+
|
|
87
|
+
### Holding it against the schema
|
|
88
|
+
|
|
89
|
+
<!-- name: test_readme_names_are_published -->
|
|
90
|
+
```python
|
|
91
|
+
from axio_responses import Responses
|
|
92
|
+
|
|
93
|
+
PUBLISHED_EVENTS = {"response.output_text.delta", "response.completed", "response.refusal.delta"}
|
|
94
|
+
|
|
95
|
+
# Every name the reader claims is one the schema publishes. A typo is a handler that never runs.
|
|
96
|
+
assert Responses.names() >= PUBLISHED_EVENTS
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`names()` answers what the reader claims, so a test can hold it against the union OpenAI publishes.
|
|
100
|
+
The check is `<=`, not `==`: the reader deliberately names fewer events than the API sends. Reading
|
|
101
|
+
with `strict=True` raises `UnknownEvent` on a name it does not claim, which is how a test fails on
|
|
102
|
+
the day OpenAI adds one.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# axio-responses
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/axio-responses/)
|
|
4
|
+
[](https://pypi.org/project/axio-responses/)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
The OpenAI Responses API as [axio](https://github.com/mosquito/axio-agent) speaks it: request items
|
|
8
|
+
in, `StreamEvent`s out.
|
|
9
|
+
|
|
10
|
+
Both halves live here rather than in a transport because two transports speak this API — the public
|
|
11
|
+
`/v1/responses` endpoint and the ChatGPT backend Codex uses. It knows nothing about HTTP and opens
|
|
12
|
+
no connection.
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install axio-responses
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Building the request
|
|
23
|
+
|
|
24
|
+
<!-- name: test_readme_building_a_request -->
|
|
25
|
+
```python
|
|
26
|
+
from axio.blocks import TextBlock
|
|
27
|
+
from axio.messages import Message
|
|
28
|
+
from axio_responses import convert_messages, convert_tools
|
|
29
|
+
|
|
30
|
+
messages, system, tools = [Message(role="user", content=[TextBlock(text="hi")])], "be brief", []
|
|
31
|
+
|
|
32
|
+
instructions, items = convert_messages(messages, system)
|
|
33
|
+
payload = {
|
|
34
|
+
"model": "gpt-5.6",
|
|
35
|
+
"instructions": instructions,
|
|
36
|
+
"input": items,
|
|
37
|
+
"stream": True,
|
|
38
|
+
"tools": convert_tools(tools),
|
|
39
|
+
}
|
|
40
|
+
assert payload["instructions"] == "be brief"
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`convert_messages` returns the system prompt separately, because this API takes it as
|
|
44
|
+
`instructions` rather than as a message. Tool calls and their outputs become `function_call` and
|
|
45
|
+
`function_call_output` items beside the messages, not blocks inside them.
|
|
46
|
+
|
|
47
|
+
### Reading the stream
|
|
48
|
+
|
|
49
|
+
`Responses` is an `axio_sse.Reader`: one `@on(...)` method per event, dispatching on the payload's
|
|
50
|
+
own `type`. Its class body names only the events it interprets. The API publishes one event family
|
|
51
|
+
per tool it can run, so that set grows with the tools and not with the protocol; everything else is
|
|
52
|
+
forwarded through `unmatched()` rather than dropped.
|
|
53
|
+
|
|
54
|
+
<!-- name: test_readme_reading_the_stream -->
|
|
55
|
+
```python
|
|
56
|
+
from collections.abc import AsyncIterator
|
|
57
|
+
|
|
58
|
+
import aiohttp
|
|
59
|
+
from axio.events import StreamEvent
|
|
60
|
+
from axio_responses import Responses
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
async def stream(resp: aiohttp.ClientResponse) -> AsyncIterator[StreamEvent]:
|
|
64
|
+
turn = Responses()
|
|
65
|
+
async for made in turn.over(resp.content.iter_any(), until="[DONE]"):
|
|
66
|
+
yield made
|
|
67
|
+
yield turn.finished()
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Events axio has no type for — the API's own hosted tools, its audio, its bookkeeping — travel as
|
|
71
|
+
`ProviderEvent` under the provider's own name rather than being dropped.
|
|
72
|
+
|
|
73
|
+
### Holding it against the schema
|
|
74
|
+
|
|
75
|
+
<!-- name: test_readme_names_are_published -->
|
|
76
|
+
```python
|
|
77
|
+
from axio_responses import Responses
|
|
78
|
+
|
|
79
|
+
PUBLISHED_EVENTS = {"response.output_text.delta", "response.completed", "response.refusal.delta"}
|
|
80
|
+
|
|
81
|
+
# Every name the reader claims is one the schema publishes. A typo is a handler that never runs.
|
|
82
|
+
assert Responses.names() >= PUBLISHED_EVENTS
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`names()` answers what the reader claims, so a test can hold it against the union OpenAI publishes.
|
|
86
|
+
The check is `<=`, not `==`: the reader deliberately names fewer events than the API sends. Reading
|
|
87
|
+
with `strict=True` raises `UnknownEvent` on a name it does not claim, which is how a test fails on
|
|
88
|
+
the day OpenAI adds one.
|
|
89
|
+
|
|
90
|
+
## License
|
|
91
|
+
|
|
92
|
+
MIT
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "axio-responses"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "The OpenAI Responses API as axio speaks it: request items in, StreamEvents out"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.12"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
keywords = ["openai", "responses", "llm", "agent", "streaming"]
|
|
9
|
+
dependencies = ["axio", "axio-sse"]
|
|
10
|
+
|
|
11
|
+
[project.urls]
|
|
12
|
+
Documentation = "https://docs.axio-agent.com"
|
|
13
|
+
Homepage = "https://github.com/mosquito/axio-agent"
|
|
14
|
+
Repository = "https://github.com/mosquito/axio-agent"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["hatchling"]
|
|
18
|
+
build-backend = "hatchling.build"
|
|
19
|
+
|
|
20
|
+
[tool.hatch.build.targets.wheel]
|
|
21
|
+
packages = ["src/axio_responses"]
|
|
22
|
+
|
|
23
|
+
[tool.pytest.ini_options]
|
|
24
|
+
asyncio_mode = "auto"
|
|
25
|
+
testpaths = ["tests", "README.md"]
|
|
26
|
+
|
|
27
|
+
[tool.ruff]
|
|
28
|
+
line-length = 119
|
|
29
|
+
output-format = "concise"
|
|
30
|
+
target-version = "py312"
|
|
31
|
+
|
|
32
|
+
[tool.ruff.lint]
|
|
33
|
+
select = ["E", "F", "I", "UP"]
|
|
34
|
+
|
|
35
|
+
[tool.mypy]
|
|
36
|
+
strict = true
|
|
37
|
+
python_version = "3.12"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8",
|
|
42
|
+
"pytest-asyncio>=0.24",
|
|
43
|
+
"mypy>=1.14",
|
|
44
|
+
"ruff>=0.9",
|
|
45
|
+
"pytest-cov>=7.1.0",
|
|
46
|
+
"markdown-pytest>=0.6.0",
|
|
47
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""The OpenAI Responses API as axio speaks it.
|
|
2
|
+
|
|
3
|
+
``convert_messages`` and ``convert_tools`` build the request. ``Responses`` reads the stream it
|
|
4
|
+
answers with. Both halves are here rather than in a transport because two transports speak this
|
|
5
|
+
API: the public ``/v1/responses`` endpoint and the ChatGPT backend Codex uses.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .reader import (
|
|
11
|
+
Annotation,
|
|
12
|
+
AnnotationAdded,
|
|
13
|
+
AnnotationSource,
|
|
14
|
+
ArgumentsDelta,
|
|
15
|
+
ArgumentsDone,
|
|
16
|
+
Completed,
|
|
17
|
+
ContentPartDone,
|
|
18
|
+
Created,
|
|
19
|
+
Failed,
|
|
20
|
+
Incomplete,
|
|
21
|
+
IncompleteDetails,
|
|
22
|
+
InputDetails,
|
|
23
|
+
ItemAdded,
|
|
24
|
+
ItemDone,
|
|
25
|
+
OutputDetails,
|
|
26
|
+
OutputItem,
|
|
27
|
+
ReasoningDeltaEvent,
|
|
28
|
+
RefusalDeltaEvent,
|
|
29
|
+
ResponseError,
|
|
30
|
+
ResponseObject,
|
|
31
|
+
Responses,
|
|
32
|
+
ResponseUsage,
|
|
33
|
+
StreamFailure,
|
|
34
|
+
TextDeltaEvent,
|
|
35
|
+
)
|
|
36
|
+
from .request import STOP_REASONS, convert_messages, convert_tools, tool_output
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"STOP_REASONS",
|
|
40
|
+
"Annotation",
|
|
41
|
+
"AnnotationAdded",
|
|
42
|
+
"AnnotationSource",
|
|
43
|
+
"ArgumentsDelta",
|
|
44
|
+
"ArgumentsDone",
|
|
45
|
+
"ContentPartDone",
|
|
46
|
+
"Completed",
|
|
47
|
+
"Created",
|
|
48
|
+
"Failed",
|
|
49
|
+
"Incomplete",
|
|
50
|
+
"IncompleteDetails",
|
|
51
|
+
"InputDetails",
|
|
52
|
+
"ItemAdded",
|
|
53
|
+
"ItemDone",
|
|
54
|
+
"OutputDetails",
|
|
55
|
+
"OutputItem",
|
|
56
|
+
"ReasoningDeltaEvent",
|
|
57
|
+
"RefusalDeltaEvent",
|
|
58
|
+
"ResponseError",
|
|
59
|
+
"ResponseObject",
|
|
60
|
+
"ResponseUsage",
|
|
61
|
+
"Responses",
|
|
62
|
+
"StreamFailure",
|
|
63
|
+
"TextDeltaEvent",
|
|
64
|
+
"convert_messages",
|
|
65
|
+
"convert_tools",
|
|
66
|
+
"tool_output",
|
|
67
|
+
]
|
|
File without changes
|