Skip to content

Commit 17635dc

Browse files
feat(assistants): add support for streaming (#1233)
See the reference docs for more information: https://platform.openai.com/docs/api-reference/assistants-streaming We've also improved some of the names for the types in the assistants beta, non exhaustive list: - `CodeToolCall` -> `CodeInterpreterToolCall` - `MessageContentImageFile` -> `ImageFileContentBlock` - `MessageContentText` -> `TextContentBlock` - `ThreadMessage` -> `Message` - `ThreadMessageDeleted` -> `MessageDeleted`
1 parent ec104bf commit 17635dc

File tree

75 files changed

+4443
-485
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+4443
-485
lines changed

api.md

+53-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Shared Types
22

33
```python
4-
from openai.types import FunctionDefinition, FunctionParameters
4+
from openai.types import ErrorObject, FunctionDefinition, FunctionParameters
55
```
66

77
# Completions
@@ -177,7 +177,19 @@ Methods:
177177
Types:
178178

179179
```python
180-
from openai.types.beta import Assistant, AssistantDeleted
180+
from openai.types.beta import (
181+
Assistant,
182+
AssistantDeleted,
183+
AssistantStreamEvent,
184+
AssistantTool,
185+
CodeInterpreterTool,
186+
FunctionTool,
187+
MessageStreamEvent,
188+
RetrievalTool,
189+
RunStepStreamEvent,
190+
RunStreamEvent,
191+
ThreadStreamEvent,
192+
)
181193
```
182194

183195
Methods:
@@ -218,6 +230,7 @@ Methods:
218230
- <code title="post /threads/{thread_id}">client.beta.threads.<a href="./src/openai/resources/beta/threads/threads.py">update</a>(thread_id, \*\*<a href="src/openai/types/beta/thread_update_params.py">params</a>) -> <a href="./src/openai/types/beta/thread.py">Thread</a></code>
219231
- <code title="delete /threads/{thread_id}">client.beta.threads.<a href="./src/openai/resources/beta/threads/threads.py">delete</a>(thread_id) -> <a href="./src/openai/types/beta/thread_deleted.py">ThreadDeleted</a></code>
220232
- <code title="post /threads/runs">client.beta.threads.<a href="./src/openai/resources/beta/threads/threads.py">create_and_run</a>(\*\*<a href="src/openai/types/beta/thread_create_and_run_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/run.py">Run</a></code>
233+
- <code>client.beta.threads.<a href="./src/openai/resources/beta/threads/threads.py">create_and_run_stream</a>(\*args) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]</code>
221234

222235
### Runs
223236

@@ -235,18 +248,31 @@ Methods:
235248
- <code title="get /threads/{thread_id}/runs">client.beta.threads.runs.<a href="./src/openai/resources/beta/threads/runs/runs.py">list</a>(thread_id, \*\*<a href="src/openai/types/beta/threads/run_list_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/run.py">SyncCursorPage[Run]</a></code>
236249
- <code title="post /threads/{thread_id}/runs/{run_id}/cancel">client.beta.threads.runs.<a href="./src/openai/resources/beta/threads/runs/runs.py">cancel</a>(run_id, \*, thread_id) -> <a href="./src/openai/types/beta/threads/run.py">Run</a></code>
237250
- <code title="post /threads/{thread_id}/runs/{run_id}/submit_tool_outputs">client.beta.threads.runs.<a href="./src/openai/resources/beta/threads/runs/runs.py">submit_tool_outputs</a>(run_id, \*, thread_id, \*\*<a href="src/openai/types/beta/threads/run_submit_tool_outputs_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/run.py">Run</a></code>
251+
- <code>client.beta.threads.runs.<a href="./src/openai/resources/beta/threads/runs/runs.py">create_and_stream</a>(\*args) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]</code>
252+
- <code>client.beta.threads.runs.<a href="./src/openai/resources/beta/threads/runs/runs.py">submit_tool_outputs_stream</a>(\*args) -> AssistantStreamManager[AssistantEventHandler] | AssistantStreamManager[AssistantEventHandlerT]</code>
238253

239254
#### Steps
240255

241256
Types:
242257

243258
```python
244259
from openai.types.beta.threads.runs import (
245-
CodeToolCall,
260+
CodeInterpreterLogs,
261+
CodeInterpreterOutputImage,
262+
CodeInterpreterToolCall,
263+
CodeInterpreterToolCallDelta,
246264
FunctionToolCall,
265+
FunctionToolCallDelta,
247266
MessageCreationStepDetails,
248267
RetrievalToolCall,
268+
RetrievalToolCallDelta,
249269
RunStep,
270+
RunStepDelta,
271+
RunStepDeltaEvent,
272+
RunStepDeltaMessageDelta,
273+
ToolCall,
274+
ToolCallDelta,
275+
ToolCallDeltaObject,
250276
ToolCallsStepDetails,
251277
)
252278
```
@@ -262,19 +288,35 @@ Types:
262288

263289
```python
264290
from openai.types.beta.threads import (
265-
MessageContentImageFile,
266-
MessageContentText,
267-
ThreadMessage,
268-
ThreadMessageDeleted,
291+
Annotation,
292+
AnnotationDelta,
293+
FileCitationAnnotation,
294+
FileCitationDeltaAnnotation,
295+
FilePathAnnotation,
296+
FilePathDeltaAnnotation,
297+
ImageFile,
298+
ImageFileContentBlock,
299+
ImageFileDelta,
300+
ImageFileDeltaBlock,
301+
Message,
302+
MessageContent,
303+
MessageContentDelta,
304+
MessageDeleted,
305+
MessageDelta,
306+
MessageDeltaEvent,
307+
Text,
308+
TextContentBlock,
309+
TextDelta,
310+
TextDeltaBlock,
269311
)
270312
```
271313

272314
Methods:
273315

274-
- <code title="post /threads/{thread_id}/messages">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">create</a>(thread_id, \*\*<a href="src/openai/types/beta/threads/message_create_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/thread_message.py">ThreadMessage</a></code>
275-
- <code title="get /threads/{thread_id}/messages/{message_id}">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">retrieve</a>(message_id, \*, thread_id) -> <a href="./src/openai/types/beta/threads/thread_message.py">ThreadMessage</a></code>
276-
- <code title="post /threads/{thread_id}/messages/{message_id}">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">update</a>(message_id, \*, thread_id, \*\*<a href="src/openai/types/beta/threads/message_update_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/thread_message.py">ThreadMessage</a></code>
277-
- <code title="get /threads/{thread_id}/messages">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">list</a>(thread_id, \*\*<a href="src/openai/types/beta/threads/message_list_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/thread_message.py">SyncCursorPage[ThreadMessage]</a></code>
316+
- <code title="post /threads/{thread_id}/messages">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">create</a>(thread_id, \*\*<a href="src/openai/types/beta/threads/message_create_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/message.py">Message</a></code>
317+
- <code title="get /threads/{thread_id}/messages/{message_id}">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">retrieve</a>(message_id, \*, thread_id) -> <a href="./src/openai/types/beta/threads/message.py">Message</a></code>
318+
- <code title="post /threads/{thread_id}/messages/{message_id}">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">update</a>(message_id, \*, thread_id, \*\*<a href="src/openai/types/beta/threads/message_update_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/message.py">Message</a></code>
319+
- <code title="get /threads/{thread_id}/messages">client.beta.threads.messages.<a href="./src/openai/resources/beta/threads/messages/messages.py">list</a>(thread_id, \*\*<a href="src/openai/types/beta/threads/message_list_params.py">params</a>) -> <a href="./src/openai/types/beta/threads/message.py">SyncCursorPage[Message]</a></code>
278320

279321
#### Files
280322

examples/assistant_stream.py

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import openai
2+
3+
# gets API Key from environment variable OPENAI_API_KEY
4+
client = openai.OpenAI()
5+
6+
assistant = client.beta.assistants.create(
7+
name="Math Tutor",
8+
instructions="You are a personal math tutor. Write and run code to answer math questions.",
9+
tools=[{"type": "code_interpreter"}],
10+
model="gpt-4-1106-preview",
11+
)
12+
13+
thread = client.beta.threads.create()
14+
15+
message = client.beta.threads.messages.create(
16+
thread_id=thread.id,
17+
role="user",
18+
content="I need to solve the equation `3x + 11 = 14`. Can you help me?",
19+
)
20+
21+
print("starting run stream")
22+
23+
stream = client.beta.threads.runs.create(
24+
thread_id=thread.id,
25+
assistant_id=assistant.id,
26+
instructions="Please address the user as Jane Doe. The user has a premium account.",
27+
stream=True,
28+
)
29+
30+
for event in stream:
31+
print(event.model_dump_json(indent=2, exclude_unset=True))
32+
33+
client.beta.assistants.delete(assistant.id)

examples/assistant_stream_helpers.py

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
from __future__ import annotations
2+
3+
from typing_extensions import override
4+
5+
import openai
6+
from openai import AssistantEventHandler
7+
from openai.types.beta import AssistantStreamEvent
8+
from openai.types.beta.threads import Text, TextDelta
9+
from openai.types.beta.threads.runs import RunStep, RunStepDelta
10+
11+
12+
class EventHandler(AssistantEventHandler):
13+
@override
14+
def on_event(self, event: AssistantStreamEvent) -> None:
15+
if event.event == "thread.run.step.created":
16+
details = event.data.step_details
17+
if details.type == "tool_calls":
18+
print("Generating code to interpret:\n\n```py")
19+
elif event.event == "thread.message.created":
20+
print("\nResponse:\n")
21+
22+
@override
23+
def on_text_delta(self, delta: TextDelta, snapshot: Text) -> None:
24+
print(delta.value, end="", flush=True)
25+
26+
@override
27+
def on_run_step_done(self, run_step: RunStep) -> None:
28+
details = run_step.step_details
29+
if details.type == "tool_calls":
30+
for tool in details.tool_calls:
31+
if tool.type == "code_interpreter":
32+
print("\n```\nExecuting code...")
33+
34+
@override
35+
def on_run_step_delta(self, delta: RunStepDelta, snapshot: RunStep) -> None:
36+
details = delta.step_details
37+
if details is not None and details.type == "tool_calls":
38+
for tool in details.tool_calls or []:
39+
if tool.type == "code_interpreter" and tool.code_interpreter and tool.code_interpreter.input:
40+
print(tool.code_interpreter.input, end="", flush=True)
41+
42+
43+
def main() -> None:
44+
client = openai.OpenAI()
45+
46+
assistant = client.beta.assistants.create(
47+
name="Math Tutor",
48+
instructions="You are a personal math tutor. Write and run code to answer math questions.",
49+
tools=[{"type": "code_interpreter"}],
50+
model="gpt-4-1106-preview",
51+
)
52+
53+
try:
54+
question = "I need to solve the equation `3x + 11 = 14`. Can you help me?"
55+
56+
thread = client.beta.threads.create(
57+
messages=[
58+
{
59+
"role": "user",
60+
"content": question,
61+
},
62+
]
63+
)
64+
print(f"Question: {question}\n")
65+
66+
with client.beta.threads.runs.create_and_stream(
67+
thread_id=thread.id,
68+
assistant_id=assistant.id,
69+
instructions="Please address the user as Jane Doe. The user has a premium account.",
70+
event_handler=EventHandler(),
71+
) as stream:
72+
stream.until_done()
73+
print()
74+
finally:
75+
client.beta.assistants.delete(assistant.id)
76+
77+
78+
main()

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ dev-dependencies = [
6060
"nox",
6161
"dirty-equals>=0.6.0",
6262
"importlib-metadata>=6.7.0",
63+
"inline-snapshot >=0.7.0",
6364
"azure-identity >=1.14.1",
6465
"types-tqdm > 4",
6566
"types-pyaudio > 0"

requirements-dev.lock

+22
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ anyio==4.1.0
1515
# via openai
1616
argcomplete==3.1.2
1717
# via nox
18+
asttokens==2.4.1
19+
# via inline-snapshot
1820
attrs==23.1.0
1921
# via pytest
2022
azure-core==1.30.1
2123
# via azure-identity
2224
azure-identity==1.15.0
25+
black==24.2.0
26+
# via inline-snapshot
2327
certifi==2023.7.22
2428
# via httpcore
2529
# via httpx
@@ -28,6 +32,9 @@ cffi==1.16.0
2832
# via cryptography
2933
charset-normalizer==3.3.2
3034
# via requests
35+
click==8.1.7
36+
# via black
37+
# via inline-snapshot
3138
colorlog==6.7.0
3239
# via nox
3340
cryptography==42.0.5
@@ -41,6 +48,8 @@ distro==1.8.0
4148
# via openai
4249
exceptiongroup==1.1.3
4350
# via anyio
51+
executing==2.0.1
52+
# via inline-snapshot
4453
filelock==3.12.4
4554
# via virtualenv
4655
h11==0.14.0
@@ -57,13 +66,15 @@ idna==3.4
5766
importlib-metadata==7.0.0
5867
iniconfig==2.0.0
5968
# via pytest
69+
inline-snapshot==0.7.0
6070
msal==1.27.0
6171
# via azure-identity
6272
# via msal-extensions
6373
msal-extensions==1.1.0
6474
# via azure-identity
6575
mypy==1.7.1
6676
mypy-extensions==1.0.0
77+
# via black
6778
# via mypy
6879
nodeenv==1.8.0
6980
# via pyright
@@ -73,14 +84,18 @@ numpy==1.26.3
7384
# via pandas
7485
# via pandas-stubs
7586
packaging==23.2
87+
# via black
7688
# via msal-extensions
7789
# via nox
7890
# via pytest
7991
pandas==2.1.4
8092
# via openai
8193
pandas-stubs==2.1.4.231227
8294
# via openai
95+
pathspec==0.12.1
96+
# via black
8397
platformdirs==3.11.0
98+
# via black
8499
# via virtualenv
85100
pluggy==1.3.0
86101
# via pytest
@@ -114,24 +129,31 @@ ruff==0.1.9
114129
setuptools==68.2.2
115130
# via nodeenv
116131
six==1.16.0
132+
# via asttokens
117133
# via azure-core
118134
# via python-dateutil
119135
sniffio==1.3.0
120136
# via anyio
121137
# via httpx
122138
# via openai
123139
time-machine==2.9.0
140+
toml==0.10.2
141+
# via inline-snapshot
124142
tomli==2.0.1
143+
# via black
125144
# via mypy
126145
# via pytest
127146
tqdm==4.66.1
128147
# via openai
129148
types-pyaudio==0.2.16.20240106
130149
types-pytz==2024.1.0.20240203
131150
# via pandas-stubs
151+
types-toml==0.10.8.20240310
152+
# via inline-snapshot
132153
types-tqdm==4.66.0.2
133154
typing-extensions==4.8.0
134155
# via azure-core
156+
# via black
135157
# via mypy
136158
# via openai
137159
# via pydantic

src/openai/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@
6969
from .version import VERSION as VERSION
7070
from .lib.azure import AzureOpenAI as AzureOpenAI, AsyncAzureOpenAI as AsyncAzureOpenAI
7171
from .lib._old_api import *
72+
from .lib.streaming import (
73+
AssistantEventHandler as AssistantEventHandler,
74+
AsyncAssistantEventHandler as AsyncAssistantEventHandler,
75+
)
7276

7377
_setup_logging()
7478

0 commit comments

Comments
 (0)