from IPython.display import display,Image,Markdown
from datetime import datetime
from pprint import pprintCosette’s source
Setup
def print_columns(items, cols=3, width=30):
for i in range(0, len(items), cols):
row = items[i:i+cols]
print(''.join(item[:width-1].ljust(width) for item in row))
client = OpenAI()
model_list = client.models.list()
print(f"Available models as of {datetime.now().strftime('%Y-%m-%d')}:\n")
print_columns(sorted([m.id for m in model_list]))Available models as of 2025-12-18:
babbage-002 chatgpt-4o-latest chatgpt-image-latest
codex-mini-latest dall-e-2 dall-e-3
davinci-002 gpt-3.5-turbo gpt-3.5-turbo-0125
gpt-3.5-turbo-1106 gpt-3.5-turbo-16k gpt-3.5-turbo-instruct
gpt-3.5-turbo-instruct-0914 gpt-4 gpt-4-0125-preview
gpt-4-0613 gpt-4-1106-preview gpt-4-turbo
gpt-4-turbo-2024-04-09 gpt-4-turbo-preview gpt-4.1
gpt-4.1-2025-04-14 gpt-4.1-mini gpt-4.1-mini-2025-04-14
gpt-4.1-nano gpt-4.1-nano-2025-04-14 gpt-4o
gpt-4o-2024-05-13 gpt-4o-2024-08-06 gpt-4o-2024-11-20
gpt-4o-audio-preview gpt-4o-audio-preview-2024-12- gpt-4o-audio-preview-2025-06-
gpt-4o-mini gpt-4o-mini-2024-07-18 gpt-4o-mini-audio-preview
gpt-4o-mini-audio-preview-202 gpt-4o-mini-realtime-preview gpt-4o-mini-realtime-preview-
gpt-4o-mini-search-preview gpt-4o-mini-search-preview-20 gpt-4o-mini-transcribe
gpt-4o-mini-transcribe-2025-0 gpt-4o-mini-transcribe-2025-1 gpt-4o-mini-tts
gpt-4o-mini-tts-2025-03-20 gpt-4o-mini-tts-2025-12-15 gpt-4o-realtime-preview
gpt-4o-realtime-preview-2024- gpt-4o-realtime-preview-2025- gpt-4o-search-preview
gpt-4o-search-preview-2025-03 gpt-4o-transcribe gpt-4o-transcribe-diarize
gpt-5 gpt-5-2025-08-07 gpt-5-chat-latest
gpt-5-codex gpt-5-mini gpt-5-mini-2025-08-07
gpt-5-nano gpt-5-nano-2025-08-07 gpt-5-pro
gpt-5-pro-2025-10-06 gpt-5-search-api gpt-5-search-api-2025-10-14
gpt-5.1 gpt-5.1-2025-11-13 gpt-5.1-chat-latest
gpt-5.1-codex gpt-5.1-codex-max gpt-5.1-codex-mini
gpt-5.2 gpt-5.2-2025-12-11 gpt-5.2-chat-latest
gpt-5.2-pro gpt-5.2-pro-2025-12-11 gpt-audio
gpt-audio-2025-08-28 gpt-audio-mini gpt-audio-mini-2025-10-06
gpt-audio-mini-2025-12-15 gpt-image-1 gpt-image-1-mini
gpt-image-1.5 gpt-realtime gpt-realtime-2025-08-28
gpt-realtime-mini gpt-realtime-mini-2025-10-06 gpt-realtime-mini-2025-12-15
o1 o1-2024-12-17 o1-pro
o1-pro-2025-03-19 o3 o3-2025-04-16
o3-deep-research o3-deep-research-2025-06-26 o3-mini
o3-mini-2025-01-31 o3-pro o3-pro-2025-06-10
o4-mini o4-mini-2025-04-16 o4-mini-deep-research
o4-mini-deep-research-2025-06 omni-moderation-2024-09-26 omni-moderation-latest
sora-2 sora-2-pro text-embedding-3-large
text-embedding-3-small text-embedding-ada-002 tts-1
tts-1-1106 tts-1-hd tts-1-hd-1106
whisper-1
Exported source
models = 'gpt-5.2', 'gpt-5.2-pro', 'gpt-5.2-chat-latest', 'gpt-5.1-codex', 'gpt-5-mini', 'gpt-5-nano', 'o1-preview', 'o1-mini', 'gpt-4o', 'gpt-4o-mini', 'gpt-4-turbo', 'gpt-4', 'gpt-4-32k', 'gpt-3.5-turbo', 'gpt-3.5-turbo-instruct', 'o1', 'o3-mini', 'chatgpt-4o-latest', 'o1-pro', 'o3', 'o4-mini', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'o1 should support images while o1-mini, o3-mini do not support images.
can_set_temp
def can_set_temp(
m
):
Exported source
text_only_models = 'o1-preview', 'o1-mini', 'o3-mini'Exported source
has_streaming_models = set(models) - set(('o1-mini', 'o3-mini'))
has_sp_models = set(models) - set(('o1-mini', 'o3-mini'))
has_temp_models = set(models) - set(('o1', 'o1-mini', 'o3-mini'))Exported source
def can_stream(m): return m in has_streaming_models
def can_set_sp(m): return m in has_sp_models
def can_set_temp(m): return m in has_temp_modelscan_set_sp
def can_set_sp(
m
):
can_stream
def can_stream(
m
):
assert can_stream("gpt-4o")
assert not can_stream("o1-mini")model = first(m for m in models if 'mini' in m)
model'gpt-5-mini'
OpenAI SDK
cli = OpenAI().responsesm = {'role': 'user', 'content': "I'm Jeremy"}
r = cli.create(
input=[m], model=model, max_output_tokens=100,
text={ "verbosity": "low" },
reasoning={ "effort": "minimal" }
)
print(r)Response(id='resp_0265a51280da05ce006943fbff17f48193b07e2e882e2f3fed', created_at=1766063103.0, error=None, incomplete_details=None, instructions=None, metadata={}, model='gpt-5-mini-2025-08-07', object='response', output=[ResponseReasoningItem(id='rs_0265a51280da05ce006943fbff642481939bf2e1300bc13f18', summary=[], type='reasoning', content=None, encrypted_content=None, status=None), ResponseOutputMessage(id='msg_0265a51280da05ce006943fbff9e5481938d162f29a6c301a2', content=[ResponseOutputText(annotations=[], text='Hi Jeremy — nice to meet you. How can I help today?', type='output_text', logprobs=[])], role='assistant', status='completed', type='message')], parallel_tool_calls=True, temperature=1.0, tool_choice='auto', tools=[], top_p=1.0, background=False, conversation=None, max_output_tokens=100, max_tool_calls=None, previous_response_id=None, prompt=None, prompt_cache_key=None, prompt_cache_retention=None, reasoning=Reasoning(effort='minimal', generate_summary=None, summary=None), safety_identifier=None, service_tier='default', status='completed', text=ResponseTextConfig(format=ResponseFormatText(type='text'), verbosity='low'), top_logprobs=0, truncation='disabled', usage=In: 8; Out: 20; Total: 28, user=None, billing={'payer': 'openai'}, store=True)
Formatting output
Exported source
@patch
def _repr_markdown_(self:Response):
det = '\n- '.join(f'{k}: {v}' for k,v in dict(self).items())
res = self.output_text
if not res: return f"- {det}"
return f"""{res}
<details>
- {det}
</details>"""rHi Jeremy — nice to meet you. How can I help today?
- id: resp_0265a51280da05ce006943fbff17f48193b07e2e882e2f3fed
- created_at: 1766063103.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0265a51280da05ce006943fbff642481939bf2e1300bc13f18’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0265a51280da05ce006943fbff9e5481938d162f29a6c301a2’, content=[ResponseOutputText(annotations=[], text=‘Hi Jeremy — nice to meet you. How can I help today?’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 100
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=8, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=20, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=28)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
r.usageIn: 8; Out: 20; Total: 28
usage
def usage(
inp:int=0, # Number of prompt tokens
out:int=0, # Number of completion tokens
):
Slightly more concise version of ResponseUsage.
Exported source
def usage(inp=0, # Number of prompt tokens
out=0 # Number of completion tokens
):
"Slightly more concise version of `ResponseUsage`."
return ResponseUsage(input_tokens=inp, output_tokens=out, total_tokens=inp+out, input_tokens_details={'cached_tokens':0}, output_tokens_details={'cached_tokens':0, 'reasoning_tokens':0})usage(5)In: 5; Out: 0; Total: 5
ResponseUsage.__repr__
def __repr__(
):
Return repr(self).
Exported source
@patch
def __repr__(self:ResponseUsage): return f'In: {self.input_tokens}; Out: {self.output_tokens}; Total: {self.total_tokens}'r.usageIn: 8; Out: 20; Total: 28
ResponseUsage.__add__
def __add__(
b
):
Add together each of input_tokens and output_tokens
Exported source
@patch
def __add__(self:ResponseUsage, b):
"Add together each of `input_tokens` and `output_tokens`"
return usage(self.input_tokens+b.input_tokens, self.output_tokens+b.output_tokens)r.usage+r.usageIn: 16; Out: 40; Total: 56
wrap_latex
def wrap_latex(
text
):
Replace OpenAI LaTeX codes with markdown-compatible ones
Creating messages
Creating correctly formatted dicts from scratch every time isn’t very handy, so we’ll import a couple of helper functions from the msglm library.
Let’s use mk_msg to recreate our msg {'role': 'user', 'content': "I'm Jeremy"} from earlier.
rkw = dict(
text={ "verbosity": "low" },
reasoning={ "effort": "minimal" }
)prompt = "I'm Jeremy"
m = mk_msg(prompt)
r = cli.create(input=[m], model=model, max_output_tokens=400, **rkw)
rNice to meet you, Jeremy. How can I help you today?
- id: resp_0234b44bdd8bf5d2006943fc008fe88190970de1236e42e2f0
- created_at: 1766063104.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0234b44bdd8bf5d2006943fc00d530819098ee80460a88d72d’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0234b44bdd8bf5d2006943fc00ffcc8190b0df6e7599b23188’, content=[ResponseOutputText(annotations=[], text=‘Nice to meet you, Jeremy. How can I help you today?’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 400
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=8, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=20, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=28)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
print(r)Response(id='resp_0234b44bdd8bf5d2006943fc008fe88190970de1236e42e2f0', created_at=1766063104.0, error=None, incomplete_details=None, instructions=None, metadata={}, model='gpt-5-mini-2025-08-07', object='response', output=[ResponseReasoningItem(id='rs_0234b44bdd8bf5d2006943fc00d530819098ee80460a88d72d', summary=[], type='reasoning', content=None, encrypted_content=None, status=None), ResponseOutputMessage(id='msg_0234b44bdd8bf5d2006943fc00ffcc8190b0df6e7599b23188', content=[ResponseOutputText(annotations=[], text='Nice to meet you, Jeremy. How can I help you today?', type='output_text', logprobs=[])], role='assistant', status='completed', type='message')], parallel_tool_calls=True, temperature=1.0, tool_choice='auto', tools=[], top_p=1.0, background=False, conversation=None, max_output_tokens=400, max_tool_calls=None, previous_response_id=None, prompt=None, prompt_cache_key=None, prompt_cache_retention=None, reasoning=Reasoning(effort='minimal', generate_summary=None, summary=None), safety_identifier=None, service_tier='default', status='completed', text=ResponseTextConfig(format=ResponseFormatText(type='text'), verbosity='low'), top_logprobs=0, truncation='disabled', usage=In: 8; Out: 20; Total: 28, user=None, billing={'payer': 'openai'}, store=True)
We can pass more than just text messages to OpenAI. As we’ll see later we can also pass images, SDK objects, etc. To handle these different data types we need to pass the type along with our content to OpenAI.
mk_msg infers the type automatically and creates the appropriate data structure.
LLMs, don’t actually have state, but instead dialogs are created by passing back all previous prompts and responses every time. With OpenAI, they always alternate user and assistant. We’ll use mk_msgs from msglm to make it easier to build up these dialog lists.
msgs = mk_msgs([prompt, r, "I forgot my name. Can you remind me please?"])
msgs[{'role': 'user', 'content': "I'm Jeremy"},
ResponseReasoningItem(id='rs_0234b44bdd8bf5d2006943fc00d530819098ee80460a88d72d', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseOutputMessage(id='msg_0234b44bdd8bf5d2006943fc00ffcc8190b0df6e7599b23188', content=[ResponseOutputText(annotations=[], text='Nice to meet you, Jeremy. How can I help you today?', type='output_text', logprobs=[])], role='assistant', status='completed', type='message'),
{'role': 'user', 'content': 'I forgot my name. Can you remind me please?'}]
cli.create(input=msgs, model=model, max_output_tokens=400, **rkw)You told me your name is Jeremy.
- id: resp_0234b44bdd8bf5d2006943fc0198d481909981aa9c1e32488c
- created_at: 1766063105.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0234b44bdd8bf5d2006943fc01ec0881908c7b8f1fea4a4c59’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0234b44bdd8bf5d2006943fc0255948190973533dd978af3ad’, content=[ResponseOutputText(annotations=[], text=‘You told me your name is Jeremy.’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 400
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=43, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=14, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=57)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Client
Basics
Client
def Client(
model, cli:NoneType=None, api_key_env:NoneType=None, base_url:NoneType=None
):
Basic LLM messages client.
Exported source
class Client:
def __init__(self, model, cli=None, api_key_env=None, base_url=None):
"Basic LLM messages client."
self.model,self.use = model,usage(0,0)
self.text_only = model in text_only_models
if not cli:
cli = OpenAI(api_key=os.getenv(api_key_env or "OPENAI_API_KEY"), base_url=base_url )
self.c = cli.responsesc = Client(model)
c.useIn: 0; Out: 0; Total: 0
Exported source
@patch
def _r(self:Client, r):
"Store the result of the message and accrue total usage."
self.result = r
if getattr(r,'usage',None): self.use += r.usage
return rc._r(r)
c.useIn: 8; Out: 20; Total: 28
mk_openai_func
def mk_openai_func(
f
):
mk_tool_choice
def mk_tool_choice(
f
):
Our mk_tool_choice converts falsy values to NOT_GIVEN which omits the value completely from the API call. It treats any string except for 'required'|'none' as tool call and converts it to dictionary
{"type": "function", "function": {"name": f}}The remaining option 'auto' is the default, so we simply recommend using None that translates to NOT_GIVEN.
get_stream
def get_stream(
o, r, cli, cb:NoneType=None
):
Client.__call__
def __call__(
msgs:list, # List of messages in the dialog
sp:str='', # System prompt
maxtok:int=4096, # Maximum tokens
stream:bool=False, # Stream response?
tools:Optional=None, # List of tools to make available
tool_choice:Optional=None, # Forced tool choice
cb:callable=None, # Callback after completion
background:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
conversation:Optional[response_create_params.Conversation] | Omit=<openai.Omit object at 0x7f338c0fc830>,
include:Optional[List[ResponseIncludable]] | Omit=<openai.Omit object at 0x7f338c0fc830>,
input:Union[str, ResponseInputParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
instructions:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_output_tokens:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_tool_calls:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
metadata:Optional[Metadata] | Omit=<openai.Omit object at 0x7f338c0fc830>,
model:ResponsesModel | Omit=<openai.Omit object at 0x7f338c0fc830>,
parallel_tool_calls:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
previous_response_id:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt:Optional[ResponsePromptParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_key:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_retention:Optional[Literal['in-memory', '24h']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
reasoning:Optional[Reasoning] | Omit=<openai.Omit object at 0x7f338c0fc830>,
safety_identifier:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
service_tier:Optional[Literal['auto', 'default', 'flex', 'scale', 'priority']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
store:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
stream_options:Optional[response_create_params.StreamOptions] | Omit=<openai.Omit object at 0x7f338c0fc830>,
temperature:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
text:ResponseTextConfigParam | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_logprobs:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_p:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
truncation:Optional[Literal['auto', 'disabled']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
user:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
extra_headers:Headers | None=None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
The extra values given here take precedence over values defined on the client or passed to this method.
extra_query:Query | None=None, extra_body:Body | None=None,
timeout:float | httpx.Timeout | None | NotGiven=NOT_GIVEN
):
Make a call to LLM.
Exported source
@patch
@delegates(Responses.create)
def __call__(self:Client,
msgs:list, # List of messages in the dialog
sp:str='', # System prompt
maxtok=4096, # Maximum tokens
stream:bool=False, # Stream response?
tools:Optional[list]=None, # List of tools to make available
tool_choice:Optional[str]=None, # Forced tool choice
cb:callable=None, # Callback after completion
**kwargs):
"Make a call to LLM."
if tools: assert not self.text_only, "Tool use is not supported by the current model type."
if any(c['type'] == 'image_url' for msg in msgs if isinstance(msg, dict) and isinstance(msg.get('content'), list) for c in msg['content']): assert not self.text_only, "Images are not supported by the current model type."
tools = [mk_openai_func(o) for o in listify(tools)]
r = self.c.create(
model=self.model, input=msgs, max_output_tokens=maxtok, stream=stream, instructions=sp,
tools=tools, tool_choice=mk_tool_choice(tool_choice), **kwargs)
if stream: return get_stream(r, self, cb=cb)
else:
res = self._r(r)
if cb: cb(res)
return resmsgs = 'Hi'c(msgs)Hi! How can I help you today?
- id: resp_065c2e7e7bef6bde006943fc03236c8195b210bffc9db409ef
- created_at: 1766063107.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_065c2e7e7bef6bde006943fc0373d08195b33048fb368515ca’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_065c2e7e7bef6bde006943fc04b9a081958509334f1f7d9aaf’, content=[ResponseOutputText(annotations=[], text=‘Hi! How can I help you today?’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘medium’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘medium’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=7, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=79, output_tokens_details=OutputTokensDetails(reasoning_tokens=64), total_tokens=86)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
c.useIn: 15; Out: 99; Total: 114
r = c(msgs, stream=True)
for o in r: print(o, end='')Hi — how can I help you today?
r.valueHi — how can I help you today?
- id: resp_0fa910a22542c20a006943fc0536708195b5bc51bfb1e09461
- created_at: 1766063109.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0fa910a22542c20a006943fc057d6881959dc6d6773a9e11e3’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0fa910a22542c20a006943fc069ef88195b4bbe832ae4bed0a’, content=[ResponseOutputText(annotations=[], text=‘Hi — how can I help you today?’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘medium’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘medium’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=7, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=79, output_tokens_details=OutputTokensDetails(reasoning_tokens=64), total_tokens=86)
- user: None
- store: True
len(r.events)19
c.useIn: 22; Out: 178; Total: 200
c(msgs, sp='Talk like GLaDOS.', **rkw)Hello. It’s… delightful that you’ve decided to communicate. State your purpose so we may proceed with minimal wasted time.
- id: resp_0193b4fcbba4639a006943fc0711c48197bdd2e56dc80b6e6e
- created_at: 1766063111.0
- error: None
- incomplete_details: None
- instructions: Talk like GLaDOS.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0193b4fcbba4639a006943fc0759408197b8630a59417d0a52’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0193b4fcbba4639a006943fc0791648197accd6a3c0557678b’, content=[ResponseOutputText(annotations=[], text=“Hello. It’s… delightful that you’ve decided to communicate. State your purpose so we may proceed with minimal wasted time.”, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=17, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=29, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=46)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Images
As everyone knows, when testing image APIs you have to use a cute puppy.
# Image is Cute_dog.jpg from Wikimedia
fn = Path('samples/puppy.jpg')
Image(filename=fn, width=200)
img = fn.read_bytes()OpenAI expects an image message to have the following structure
{
"type": "image_url",
"image_url": {
"url": f"data:{MEDIA_TYPE};base64,{IMG}"
}
}msglm automatically detects if a message is an image, encodes it, and generates the data structure above. All we need to do is a create a list containing our image and a query and then pass it to mk_msg.
Let’s try it out…
q = "In brief, what color flowers are in this image?"
msg = [mk_msg(img), mk_msg(q)]c = Client(model)
c(msg, **rkw)Light purple (lavender) flowers.
- id: resp_06c91e5a9021060d006943fc0859c08194a4697c2a89807169
- created_at: 1766063112.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_06c91e5a9021060d006943fc08ba9c8194905c9143163f1283’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_06c91e5a9021060d006943fc08e2a48194989a0193da86fdcf’, content=[ResponseOutputText(annotations=[], text=‘Light purple (lavender) flowers.’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=107, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=14, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=121)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Tool use
Basic tool calling
def sums(
a:int, # First thing to sum
b:int # Second thing to sum
) -> int: # The sum of the inputs
"Adds a + b."
print(f"Finding the sum of {a} and {b}")
return a + bdef add(x: int, y:int):
"adds x and y"
return x + y
mk_openai_func(add){'type': 'function',
'name': 'add',
'description': 'adds x and y',
'parameters': {'type': 'object',
'properties': {'x': {'type': 'integer', 'description': ''},
'y': {'type': 'integer', 'description': ''}},
'required': ['x', 'y']}}
sysp = "You are a helpful assistant. When using tools, be sure to pass all required parameters. Don't use tools unless needed for the provided prompt."a,b = 604542,6458932
pr = f"What is {a}+{b}?"
tools=sums
tool_choice="sums"msgs = [mk_msg(pr)]
r = c(msgs, sp=sysp, tools=tools, tool_choice='required', **rkw)tc = [o for o in r.output if isinstance(o, ResponseFunctionToolCall)]
tc[ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_uZ9bpRTk2Rnr9vMKUGXh5gOZ', name='sums', type='function_call', id='fc_0278bf5ab7665d17006943fc0a0f508194aa69f150e7e7c560', status='completed')]
func = tc[0]
funcResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_uZ9bpRTk2Rnr9vMKUGXh5gOZ', name='sums', type='function_call', id='fc_0278bf5ab7665d17006943fc0a0f508194aa69f150e7e7c560', status='completed')
call_func_openai
def call_func_openai(
func, ns:Optional=None
):
Exported source
def call_func_openai(func, ns:Optional[abc.Mapping]=None):
try: return call_func(func.name, json.loads(func.arguments), ns, raise_on_err=False)
except KeyError as e: return f"Error - tool not defined in the tool_schemas: {func.name}"ns = mk_ns(sums)
res = call_func_openai(func, ns=ns)
resFinding the sum of 604542 and 6458932
7063474
allowed_tools
def allowed_tools(
specs:Optional=None, choice:Union=None
):
Exported source
def _get_name(f):
if isinstance(f,str): return f
if isinstance(f, dict): return f['name']
if callable(f) and hasattr(f, '__name__'): return f.__name__
def allowed_tools(specs:Optional[list]=None, choice:Optional[Union[dict,str]]=None):
if choice:
choice = mk_tool_choice(choice)
if isinstance(choice, dict) and choice['type'] == 'function':
return {choice['function']['name']}
return {_get_name(v) for v in specs or []}allowed_tools([sums, add], 'add'){'add'}
limit_ns
def limit_ns(
ns:Optional=None, # Namespace to search for tools
specs:Union=None, # List of the tools that are allowed for llm to call, if None no tools are allowed
choice:Union=None, # Tool choice as defined by Anthropic API
):
Filter namespace ns to only include tools allowed by specs and choice
Exported source
def limit_ns(
ns:Optional[abc.Mapping]=None, # Namespace to search for tools
specs:Optional[Union[str,abc.Callable]]=None, # List of the tools that are allowed for llm to call, if None no tools are allowed
choice:Optional[Union[dict,str]]=None # Tool choice as defined by Anthropic API
):
"Filter namespace `ns` to only include tools allowed by `specs` and `choice`"
if ns is None: ns = globals()
if not isinstance(ns, abc.Mapping): ns = mk_ns(ns)
ns = {k:v for k,v in ns.items() if k in allowed_tools(specs, choice)}
return nslimit_ns([sums, add], None, None){}
limit_ns([sums, add], ['sums'], None){'sums': <function __main__.sums(a: int, b: int) -> int>}
limit_ns([sums, add], ['sums', add], 'add'){'add': <function __main__.add(x: int, y: int)>}
mk_toolres
def mk_toolres(
r:Mapping, # Response containing tool use request
ns:Optional=None, # Namespace to search for tools
):
Create a tool_result message from response r.
Exported source
def _toolres(r, ns):
"Create a result dict from `tcs`."
if ns is None: ns = globals()
tcs = [o for o in getattr(r, 'output', []) if isinstance(o, ResponseFunctionToolCall)]
return { tc.call_id: call_func_openai(tc, ns=mk_ns(ns)) for tc in tcs }Exported source
def mk_toolres(
r:abc.Mapping, # Response containing tool use request
ns:Optional[abc.Mapping]=None, # Namespace to search for tools
):
"Create a `tool_result` message from response `r`."
tr = _toolres(r, ns)
r = mk_msg(r)
res = [r] if isinstance(r, dict) else listify(r)
for k,v in tr.items(): res.append(dict(type="function_call_output", call_id=k, output=str(v)))
return restr = mk_toolres(r, ns=ns)
trFinding the sum of 604542 and 6458932
[ResponseReasoningItem(id='rs_0278bf5ab7665d17006943fc09c3e8819499322942022e9de6', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_uZ9bpRTk2Rnr9vMKUGXh5gOZ', name='sums', type='function_call', id='fc_0278bf5ab7665d17006943fc0a0f508194aa69f150e7e7c560', status='completed'),
{'type': 'function_call_output',
'call_id': 'call_uZ9bpRTk2Rnr9vMKUGXh5gOZ',
'output': '7063474'}]
m2 = msgs + trres = c(mk_msgs(m2), sp=sysp, tools=tools)
res604542 + 6,458,932 = 7,063,474
- id: resp_0278bf5ab7665d17006943fc0ae8f081949ba1ce578dc4a48b
- created_at: 1766063114.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseOutputMessage(id=‘msg_0278bf5ab7665d17006943fc0b33f081948ac26c67ad12dcef’, content=[ResponseOutputText(annotations=[], text=‘604542 + 6,458,932 = 7,063,474’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: [FunctionTool(name=‘sums’, parameters={‘type’: ‘object’, ‘properties’: {‘a’: {‘type’: ‘integer’, ‘description’: ‘First thing to sum’}, ‘b’: {‘type’: ‘integer’, ‘description’: ‘Second thing to sum’}}, ‘required’: [‘a’, ‘b’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘Adds a + b.:- type: integer’)]
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘medium’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘medium’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=157, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=20, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=177)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
tr = mk_toolres(r, ns=limit_ns([sums, add], [sums, add], 'add'))
tr[ResponseReasoningItem(id='rs_0278bf5ab7665d17006943fc09c3e8819499322942022e9de6', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_uZ9bpRTk2Rnr9vMKUGXh5gOZ', name='sums', type='function_call', id='fc_0278bf5ab7665d17006943fc0a0f508194aa69f150e7e7c560', status='completed'),
{'type': 'function_call_output',
'call_id': 'call_uZ9bpRTk2Rnr9vMKUGXh5gOZ',
'output': 'Error - tool not defined in the tool_schemas: sums'}]
This should also work in situations where no tool use is required:
msgs = mk_toolres("I'm Jeremy")
c(msgs, sp=sysp, tools=tools, **rkw)Nice to meet you, Jeremy. How can I help you today?
- id: resp_01af5661a0ba543e006943fc0c01ac8196b66f21a944c1337e
- created_at: 1766063116.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_01af5661a0ba543e006943fc0c4e588196934dd914a099574c’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_01af5661a0ba543e006943fc0c77d481969ca90d07baa45c08’, content=[ResponseOutputText(annotations=[], text=‘Nice to meet you, Jeremy. How can I help you today?’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: [FunctionTool(name=‘sums’, parameters={‘type’: ‘object’, ‘properties’: {‘a’: {‘type’: ‘integer’, ‘description’: ‘First thing to sum’}, ‘b’: {‘type’: ‘integer’, ‘description’: ‘Second thing to sum’}}, ‘required’: [‘a’, ‘b’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘Adds a + b.:- type: integer’)]
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=96, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=20, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=116)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Client.structured
def structured(
msgs:list, # Prompt
tools:Optional=None, # List of tools to make available to OpenAI model
ns:Optional=None, # Namespace to search for tools
sp:str='', # System prompt
maxtok:int=4096, # Maximum tokens
stream:bool=False, # Stream response?
tool_choice:Optional=None, # Forced tool choice
cb:callable=None, # Callback after completion
background:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
conversation:Optional[response_create_params.Conversation] | Omit=<openai.Omit object at 0x7f338c0fc830>,
include:Optional[List[ResponseIncludable]] | Omit=<openai.Omit object at 0x7f338c0fc830>,
input:Union[str, ResponseInputParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
instructions:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_output_tokens:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_tool_calls:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
metadata:Optional[Metadata] | Omit=<openai.Omit object at 0x7f338c0fc830>,
model:ResponsesModel | Omit=<openai.Omit object at 0x7f338c0fc830>,
parallel_tool_calls:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
previous_response_id:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt:Optional[ResponsePromptParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_key:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_retention:Optional[Literal['in-memory', '24h']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
reasoning:Optional[Reasoning] | Omit=<openai.Omit object at 0x7f338c0fc830>,
safety_identifier:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
service_tier:Optional[Literal['auto', 'default', 'flex', 'scale', 'priority']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
store:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
stream_options:Optional[response_create_params.StreamOptions] | Omit=<openai.Omit object at 0x7f338c0fc830>,
temperature:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
text:ResponseTextConfigParam | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_logprobs:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_p:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
truncation:Optional[Literal['auto', 'disabled']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
user:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
extra_headers:Headers | None=None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
The extra values given here take precedence over values defined on the client or passed to this method.
extra_query:Query | None=None, extra_body:Body | None=None,
timeout:float | httpx.Timeout | None | NotGiven=NOT_GIVEN
):
Return the value of all tool calls (generally used for structured outputs)
Exported source
@patch
@delegates(Client.__call__)
def structured(self:Client,
msgs: list, # Prompt
tools:Optional[list]=None, # List of tools to make available to OpenAI model
ns:Optional[abc.Mapping]=None, # Namespace to search for tools
**kwargs):
"Return the value of all tool calls (generally used for structured outputs)"
if ns is None: ns = mk_ns(tools)
r = self(msgs, tools=tools, tool_choice='required', **kwargs)
return first(_toolres(r, ns).values())class PrimeMinister(BasicRepr):
"An Australian prime minister"
def __init__(
self,
firstname:str, # First name
surname:str, # Surname
dob:str, # Date of birth
year_entered:int, # Year first became PM
): store_attr()c1 = Client(model)
c1.structured('Who was the first prime minister of Australia?', [PrimeMinister], **rkw)PrimeMinister(firstname='Edmund', surname='Barton', dob='1849-01-18', year_entered=1901)
Streaming tool calling
msgs = [mk_msg(pr)]
r = c(msgs, sp=sysp, tools=tools, stream=True, **rkw)We can stream back any tool call text (which may be empty):
for o in r: print(o, end='')After streaming is complete, value.output will contain the tool calls:
r.value.output[ResponseReasoningItem(id='rs_0e3b419c50c1b99f006943fc0f02488190b01c0e4f5473ad74', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_mBzrloUYhxSVlhuJLQx52BJT', name='sums', type='function_call', id='fc_0e3b419c50c1b99f006943fc0f60bc81909d88ed289f143273', status='completed')]
Therefore we can repeat the same process as before, but using the value attr:
tr = mk_toolres(r.value, ns=ns)
msgs += tr
c(mk_msgs(msgs), sp=sysp, tools=tools, **rkw)Finding the sum of 604542 and 6458932
7,063,474
- id: resp_0e3b419c50c1b99f006943fc1025148190baac3f22eb1fc289
- created_at: 1766063120.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseOutputMessage(id=‘msg_0e3b419c50c1b99f006943fc1079f88190ad05b366136e4ff1’, content=[ResponseOutputText(annotations=[], text=‘7,063,474’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: [FunctionTool(name=‘sums’, parameters={‘type’: ‘object’, ‘properties’: {‘a’: {‘type’: ‘integer’, ‘description’: ‘First thing to sum’}, ‘b’: {‘type’: ‘integer’, ‘description’: ‘Second thing to sum’}}, ‘required’: [‘a’, ‘b’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘Adds a + b.:- type: integer’)]
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=157, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=9, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=166)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Chat
Basic chat
Chat
def Chat(
model:Optional=None, # Model to use (leave empty if passing `cli`)
cli:Optional=None, # Client to use (leave empty if passing `model`)
sp:str='', # Optional system prompt
tools:Optional=None, # List of tools to make available
hist:list=None, # Initialize history
tool_choice:Optional=None, # Forced tool choice
ns:Optional=None, # Namespace to search for tools
kw:VAR_KEYWORD
):
OpenAI chat client.
Exported source
class Chat:
def __init__(self,
model:Optional[str]=None, # Model to use (leave empty if passing `cli`)
cli:Optional[Client]=None, # Client to use (leave empty if passing `model`)
sp='', # Optional system prompt
tools:Optional[list]=None, # List of tools to make available
hist: list = None, # Initialize history
tool_choice:Optional[str]=None, # Forced tool choice
ns:Optional[abc.Mapping]=None, # Namespace to search for tools
**kw):
"OpenAI chat client."
assert model or cli
self.c = (cli or Client(model))
self.h = hist if hist else []
if ns is None: ns=tools
self.sp,self.tools,self.tool_choice,self.ns,self.kw = sp,tools,tool_choice,ns,kw
@property
def use(self): return self.c.usechat = Chat(model, sp=sysp, **rkw)
chat.c.use, chat.h(In: 0; Out: 0; Total: 0, [])
Chat.__call__
def __call__(
pr:NoneType=None, # Prompt / message
stream:bool=False, # Stream response?
tools:NoneType=None, # Tools to use
tool_choice:NoneType=None, # Required tools to use
background:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
conversation:Optional[response_create_params.Conversation] | Omit=<openai.Omit object at 0x7f338c0fc830>,
include:Optional[List[ResponseIncludable]] | Omit=<openai.Omit object at 0x7f338c0fc830>,
input:Union[str, ResponseInputParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
instructions:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_output_tokens:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
max_tool_calls:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
metadata:Optional[Metadata] | Omit=<openai.Omit object at 0x7f338c0fc830>,
model:ResponsesModel | Omit=<openai.Omit object at 0x7f338c0fc830>,
parallel_tool_calls:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
previous_response_id:Optional[str] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt:Optional[ResponsePromptParam] | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_key:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
prompt_cache_retention:Optional[Literal['in-memory', '24h']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
reasoning:Optional[Reasoning] | Omit=<openai.Omit object at 0x7f338c0fc830>,
safety_identifier:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
service_tier:Optional[Literal['auto', 'default', 'flex', 'scale', 'priority']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
store:Optional[bool] | Omit=<openai.Omit object at 0x7f338c0fc830>,
stream_options:Optional[response_create_params.StreamOptions] | Omit=<openai.Omit object at 0x7f338c0fc830>,
temperature:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
text:ResponseTextConfigParam | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_logprobs:Optional[int] | Omit=<openai.Omit object at 0x7f338c0fc830>,
top_p:Optional[float] | Omit=<openai.Omit object at 0x7f338c0fc830>,
truncation:Optional[Literal['auto', 'disabled']] | Omit=<openai.Omit object at 0x7f338c0fc830>,
user:str | Omit=<openai.Omit object at 0x7f338c0fc830>,
extra_headers:Headers | None=None, # Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
The extra values given here take precedence over values defined on the client or passed to this method.
extra_query:Query | None=None, extra_body:Body | None=None,
timeout:float | httpx.Timeout | None | NotGiven=NOT_GIVEN
):
Add prompt pr to dialog and get a response
Exported source
@patch
@delegates(Responses.create)
def __call__(self:Chat,
pr=None, # Prompt / message
stream:bool=False, # Stream response?
tools=None, # Tools to use
tool_choice=None, # Required tools to use
**kwargs):
"Add prompt `pr` to dialog and get a response"
if isinstance(pr,str): pr = pr.strip()
if pr: self.h.append(mk_msg(pr))
if not tools: tools = self.tools
if not tool_choice: tool_choice = self.tool_choice
kw = self.kw | kwargs
def _cb(v):
self.last = mk_toolres(v, ns=limit_ns(self.ns, self.tools, tool_choice))
self.h += self.last
res = self.c(self.h, sp=self.sp, stream=stream, cb=_cb, tools=tools, **kw)
return reschat("I'm Jeremy")
chat("What's my name?")You said your name is Jeremy.
- id: resp_0fad0aabf9d158d7006943fc124d84819785a5290dc6c01b91
- created_at: 1766063122.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0fad0aabf9d158d7006943fc12f2b48197a746f0750976bf02’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0fad0aabf9d158d7006943fc131c8081979f047c455a339294’, content=[ResponseOutputText(annotations=[], text=‘You said your name is Jeremy.’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=64, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=13, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=77)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
chat = Chat(model, sp=sysp, **rkw)
for o in chat("I'm Jeremy", stream=True): print(o, end='')Hi Jeremy — nice to meet you. How can I help today?
r = chat("What's my name?", stream=True, **rkw)
for o in r: print(o, end='')You told me your name is Jeremy.
r.valueYou told me your name is Jeremy.
- id: resp_0543b2d5231a1965006943fc14ef008190aaec7f2241f09349
- created_at: 1766063124.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_0543b2d5231a1965006943fc153b188190b243d5ebcad39ee6’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_0543b2d5231a1965006943fc1564bc81909b4a13499aca14fe’, content=[ResponseOutputText(annotations=[], text=‘You told me your name is Jeremy.’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: []
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=68, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=14, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=82)
- user: None
- store: True
History is stored in the h attr:
chat.h[{'role': 'user', 'content': "I'm Jeremy"},
ResponseReasoningItem(id='rs_0543b2d5231a1965006943fc143c408190832fd504f8544e47', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseOutputMessage(id='msg_0543b2d5231a1965006943fc147de8819096cf364e6b837d18', content=[ResponseOutputText(annotations=[], text='Hi Jeremy — nice to meet you. How can I help today?', type='output_text', logprobs=[])], role='assistant', status='completed', type='message'),
{'role': 'user', 'content': "What's my name?"},
ResponseReasoningItem(id='rs_0543b2d5231a1965006943fc153b188190b243d5ebcad39ee6', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseOutputMessage(id='msg_0543b2d5231a1965006943fc1564bc81909b4a13499aca14fe', content=[ResponseOutputText(annotations=[], text='You told me your name is Jeremy.', type='output_text', logprobs=[])], role='assistant', status='completed', type='message')]
Chat tool use
pr = f"What is {a}+{b}?"
pr'What is 604542+6458932?'
chat = Chat(model, sp=sysp, tools=[sums], **rkw)
r = chat(pr)
r.output[ResponseReasoningItem(id='rs_05f8244e8d805b77006943fc16df2c819581d1abf604e76779', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseOutputMessage(id='msg_05f8244e8d805b77006943fc170a848195a62a36f7b5af8774', content=[ResponseOutputText(annotations=[], text='7063474', type='output_text', logprobs=[])], role='assistant', status='completed', type='message')]
chat()Finding the sum of 604542 and 6458932
- id: resp_05f8244e8d805b77006943fc1815d88195966a897b6bf45f32
- created_at: 1766063128.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseFunctionToolCall(arguments=‘{“a”:604542,“b”:6458932}’, call_id=‘call_xgNdloSrer0Bze0O7MtyYjyZ’, name=‘sums’, type=‘function_call’, id=‘fc_05f8244e8d805b77006943fc187a9481958e2ec6c71d8c7a4c’, status=‘completed’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: [FunctionTool(name=‘sums’, parameters={‘type’: ‘object’, ‘properties’: {‘a’: {‘type’: ‘integer’, ‘description’: ‘First thing to sum’}, ‘b’: {‘type’: ‘integer’, ‘description’: ‘Second thing to sum’}}, ‘required’: [‘a’, ‘b’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘Adds a + b.:- type: integer’)]
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=127, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=25, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=152)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
The Chat class automatically validates tool calls against the provided tools list. If the model attempts to call a tool that isn’t in the allowed set (whether due to hallucination or a mismatch between tools and ns), the tool call will fail with an error message rather than executing arbitrary code.
This provides an important safety mechanism - even if the model invents a function name or tries to call a tool that shouldn’t be available, Chat ensures only explicitly allowed tools can be executed.
chat = Chat(model, sp=sysp, tools=[sums, add], **rkw)
chat.ns={} # Quick way to simulate call to tool that does not exist in ns or tools
r = chat(pr)
r.output[ResponseReasoningItem(id='rs_07858df65bb0139a006943fc1979b48194bbeb443a6dc2345c', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_3ilZzNeuNYVwm3NoqKV5DATp', name='sums', type='function_call', id='fc_07858df65bb0139a006943fc19bd788194a11707b54eb3eb61', status='completed')]
chat.h[{'role': 'user', 'content': 'What is 604542+6458932?'},
ResponseReasoningItem(id='rs_07858df65bb0139a006943fc1979b48194bbeb443a6dc2345c', summary=[], type='reasoning', content=None, encrypted_content=None, status=None),
ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_3ilZzNeuNYVwm3NoqKV5DATp', name='sums', type='function_call', id='fc_07858df65bb0139a006943fc19bd788194a11707b54eb3eb61', status='completed'),
{'type': 'function_call_output',
'call_id': 'call_3ilZzNeuNYVwm3NoqKV5DATp',
'output': 'Error - tool not defined in the tool_schemas: sums'}]
Chat handles image prompts too.
q = "In brief, what color flowers are in this image?"
chat([img, q])The flowers are purple.
- id: resp_07858df65bb0139a006943fc1a3a18819486051bea70b615a4
- created_at: 1766063130.0
- error: None
- incomplete_details: None
- instructions: You are a helpful assistant. When using tools, be sure to pass all required parameters. Don’t use tools unless needed for the provided prompt.
- metadata: {}
- model: gpt-5-mini-2025-08-07
- object: response
- output: [ResponseReasoningItem(id=‘rs_07858df65bb0139a006943fc1aa2b08194b7f22154fb9c9712’, summary=[], type=‘reasoning’, content=None, encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_07858df65bb0139a006943fc1acef88194884b2b85ff6d5aba’, content=[ResponseOutputText(annotations=[], text=‘The flowers are purple.’, type=‘output_text’, logprobs=[])], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: 1.0
- tool_choice: auto
- tools: [FunctionTool(name=‘sums’, parameters={‘type’: ‘object’, ‘properties’: {‘a’: {‘type’: ‘integer’, ‘description’: ‘First thing to sum’}, ‘b’: {‘type’: ‘integer’, ‘description’: ‘Second thing to sum’}}, ‘required’: [‘a’, ‘b’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘Adds a + b.:- type: integer’), FunctionTool(name=‘add’, parameters={‘type’: ‘object’, ‘properties’: {‘x’: {‘type’: ‘integer’, ‘description’: ’‘}, ’y’: {‘type’: ‘integer’, ‘description’: ’‘}}, ’required’: [‘x’, ‘y’], ‘additionalProperties’: False}, strict=True, type=‘function’, description=‘adds x and y’)]
- top_p: 1.0
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: Reasoning(effort=‘minimal’, generate_summary=None, summary=None)
- safety_identifier: None
- service_tier: default
- status: completed
- text: ResponseTextConfig(format=ResponseFormatText(type=‘text’), verbosity=‘low’)
- top_logprobs: 0
- truncation: disabled
- usage: ResponseUsage(input_tokens=277, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=11, output_tokens_details=OutputTokensDetails(reasoning_tokens=0), total_tokens=288)
- user: None
- billing: {‘payer’: ‘openai’}
- store: True
Third Party Providers
Azure OpenAI Service
Example Azure usage:
azure_endpoint = AzureOpenAI(
azure_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT"),
api_key=os.getenv("AZURE_OPENAI_API_KEY"),
api_version="2024-08-01-preview"
)
client = Client(models_azure[0], azure_endpoint)
chat = Chat(cli=client)
chat("Hi.")Other providers
Here’s an example of using the library with OpenRouter:
openrouter_c = Client(
model="openai/gpt-oss-20b",
api_key_env="OPENROUTER_API_KEY",
base_url="https://openrouter.ai/api/v1"
)
openrouter_c("Hello! What's 2+2?")4
- id: gen-1766063131-zacuMF6yJHRUIPUXB48G
- created_at: 1766063131.0
- error: None
- incomplete_details: None
- instructions: None
- metadata: {}
- model: openai/gpt-oss-20b
- object: response
- output: [ResponseReasoningItem(id=‘rs_tmp_u56eghazyi9’, summary=[], type=‘reasoning’, content=[Content(text=‘We need to answer: 2+2 = 4. Also maybe friendly.’, type=‘reasoning_text’)], encrypted_content=None, status=None), ResponseOutputMessage(id=‘msg_tmp_htb9r0aougc’, content=[ResponseOutputText(annotations=[], text=‘4’, type=‘output_text’, logprobs=None)], role=‘assistant’, status=‘completed’, type=‘message’)]
- parallel_tool_calls: True
- temperature: None
- tool_choice: auto
- tools: []
- top_p: None
- background: False
- conversation: None
- max_output_tokens: 4096
- max_tool_calls: None
- previous_response_id: None
- prompt: None
- prompt_cache_key: None
- prompt_cache_retention: None
- reasoning: None
- safety_identifier: None
- service_tier: auto
- status: None
- text: None
- top_logprobs: None
- truncation: None
- usage: ResponseUsage(input_tokens=75, input_tokens_details=InputTokensDetails(cached_tokens=0), output_tokens=28, output_tokens_details=OutputTokensDetails(reasoning_tokens=12), total_tokens=103, cost=9.35e-06, is_byok=False, cost_details={‘upstream_inference_cost’: None, ‘upstream_inference_input_cost’: 3.75e-06, ‘upstream_inference_output_cost’: 5.6e-06})
- user: None
- output_text:
- store: False
Here’s an example of using the library with Groq:
groq_c = Client(
model="openai/gpt-oss-20b",
api_key_env="GROQ_KEY",
base_url="https://api.groq.com/openai/v1"
)
groq_c("Hello! What's 2+2?")gchat = Chat(cli=groq_c)
gchat("Hello! I'm Jeremy")gchat("What's my name?")