# Source: https://answerdotai.github.io/cosette/core.html.md

# Cosette’s source


<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->

## Setup

``` python
from IPython.display import display,Image,Markdown
from datetime import datetime
from pprint import pprint
```

``` python
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                     

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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'
```

</details>

`o1` should support images while `o1-mini`, `o3-mini` do not support
images.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L58"
target="_blank" style="float:right; font-size:smaller">source</a>

### can_set_temp

``` python

def can_set_temp(
    m
):

```

<details open class="code-fold">
<summary>Exported source</summary>

``` python
text_only_models = 'o1-preview', 'o1-mini', 'o3-mini'
```

</details>

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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'))
```

</details>

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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_models
```

</details>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L57"
target="_blank" style="float:right; font-size:smaller">source</a>

### can_set_sp

``` python

def can_set_sp(
    m
):

```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L56"
target="_blank" style="float:right; font-size:smaller">source</a>

### can_stream

``` python

def can_stream(
    m
):

```

``` python
assert can_stream("gpt-4o")
assert not can_stream("o1-mini")
```

``` python
model = first(m for m in models if 'mini' in m)
model
```

    'gpt-5-mini'

## OpenAI SDK

``` python
cli = OpenAI().responses
```

``` python
m = {'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

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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>"""
```

</details>

``` python
r
```

Hi Jeremy — nice to meet you. How can I help today?

<details>

- 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

</details>

``` python
r.usage
```

    In: 8; Out: 20; Total: 28

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L75"
target="_blank" style="float:right; font-size:smaller">source</a>

### usage

``` python

def usage(
    inp:int=0, # Number of prompt tokens
    out:int=0, # Number of completion tokens
):

```

*Slightly more concise version of `ResponseUsage`.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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})
```

</details>

``` python
usage(5)
```

    In: 5; Out: 0; Total: 5

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L83"
target="_blank" style="float:right; font-size:smaller">source</a>

### ResponseUsage.\_\_repr\_\_

``` python

def __repr__(
    
):

```

*Return repr(self).*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@patch
def __repr__(self:ResponseUsage): return f'In: {self.input_tokens}; Out: {self.output_tokens}; Total: {self.total_tokens}'
```

</details>

``` python
r.usage
```

    In: 8; Out: 20; Total: 28

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L87"
target="_blank" style="float:right; font-size:smaller">source</a>

### ResponseUsage.\_\_add\_\_

``` python

def __add__(
    b
):

```

*Add together each of `input_tokens` and `output_tokens`*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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)
```

</details>

``` python
r.usage+r.usage
```

    In: 16; Out: 40; Total: 56

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L92"
target="_blank" style="float:right; font-size:smaller">source</a>

### wrap_latex

``` python

def wrap_latex(
    text
):

```

*Replace OpenAI LaTeX codes with markdown-compatible ones*

### Creating messages

Creating correctly formatted `dict`s 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.

``` python
rkw = dict(
    text={ "verbosity": "low" },
    reasoning={ "effort": "minimal" }
)
```

``` python
prompt = "I'm Jeremy"
m = mk_msg(prompt)
r = cli.create(input=[m], model=model, max_output_tokens=400, **rkw)
r
```

Nice to meet you, Jeremy. How can I help you today?

<details>

- 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

</details>

``` python
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.

``` python
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?'}]

``` python
cli.create(input=msgs, model=model, max_output_tokens=400, **rkw)
```

You told me your name is Jeremy.

<details>

- 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

</details>

## Client

### Basics

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L99"
target="_blank" style="float:right; font-size:smaller">source</a>

### Client

``` python

def Client(
    model, cli:NoneType=None, api_key_env:NoneType=None, base_url:NoneType=None
):

```

*Basic LLM messages client.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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.responses
```

</details>

``` python
c = Client(model)
c.use
```

    In: 0; Out: 0; Total: 0

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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 r
```

</details>

``` python
c._r(r)
c.use
```

    In: 8; Out: 20; Total: 28

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L117"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_openai_func

``` python

def mk_openai_func(
    f
):

```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L124"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_tool_choice

``` python

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

``` python
  {"type": "function", "function": {"name": f}}
```

The remaining option `'auto'` is the default, so we simply recommend
using `None` that translates to `NOT_GIVEN`.

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L131"
target="_blank" style="float:right; font-size:smaller">source</a>

### get_stream

``` python

def get_stream(
    o, r, cli, cb:NoneType=None
):

```

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L144"
target="_blank" style="float:right; font-size:smaller">source</a>

### Client.\_\_call\_\_

``` python

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.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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 res
```

</details>

``` python
msgs = 'Hi'
```

``` python
c(msgs)
```

Hi! How can I help you today?

<details>

- 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

</details>

``` python
c.use
```

    In: 15; Out: 99; Total: 114

``` python
r = c(msgs, stream=True)
for o in r: print(o, end='')
```

    Hi — how can I help you today?

``` python
r.value
```

Hi — how can I help you today?

<details>

- 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

</details>

``` python
len(r.events)
```

    19

``` python
c.use
```

    In: 22; Out: 178; Total: 200

``` python
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.

<details>

- 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

</details>

### Images

As everyone knows, when testing image APIs you have to use a cute puppy.

``` python
# Image is Cute_dog.jpg from Wikimedia
fn = Path('samples/puppy.jpg')
Image(filename=fn, width=200)
```

<img src="00_core_files/figure-commonmark/cell-51-output-1.jpeg"
width="200" />

``` python
img = fn.read_bytes()
```

OpenAI expects an image message to have the following structure

``` js
{
  "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…

``` python
q = "In brief, what color flowers are in this image?"
msg = [mk_msg(img), mk_msg(q)]
```

``` python
c = Client(model)
c(msg, **rkw)
```

Light purple (lavender) flowers.

<details>

- 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

</details>

## Tool use

### Basic tool calling

``` python
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 + b
```

``` python
def 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']}}

``` python
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."
```

``` python
a,b = 604542,6458932
pr = f"What is {a}+{b}?"
tools=sums
tool_choice="sums"
```

``` python
msgs = [mk_msg(pr)]
r = c(msgs, sp=sysp, tools=tools, tool_choice='required', **rkw)
```

``` python
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')]

``` python
func = tc[0]
func
```

    ResponseFunctionToolCall(arguments='{"a":604542,"b":6458932}', call_id='call_uZ9bpRTk2Rnr9vMKUGXh5gOZ', name='sums', type='function_call', id='fc_0278bf5ab7665d17006943fc0a0f508194aa69f150e7e7c560', status='completed')

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L167"
target="_blank" style="float:right; font-size:smaller">source</a>

### call_func_openai

``` python

def call_func_openai(
    func, ns:Optional=None
):

```

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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}"
```

</details>

``` python
ns = mk_ns(sums)
res = call_func_openai(func, ns=ns)
res
```

    Finding the sum of 604542 and 6458932

    7063474

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L177"
target="_blank" style="float:right; font-size:smaller">source</a>

### allowed_tools

``` python

def allowed_tools(
    specs:Optional=None, choice:Union=None
):

```

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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 []}
```

</details>

``` python
allowed_tools([sums, add], 'add')
```

    {'add'}

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L185"
target="_blank" style="float:right; font-size:smaller">source</a>

### limit_ns

``` python

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`*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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 ns
```

</details>

``` python
limit_ns([sums, add], None, None)
```

    {}

``` python
limit_ns([sums, add], ['sums'], None)
```

    {'sums': <function __main__.sums(a: int, b: int) -> int>}

``` python
limit_ns([sums, add], ['sums', add], 'add')
```

    {'add': <function __main__.add(x: int, y: int)>}

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L204"
target="_blank" style="float:right; font-size:smaller">source</a>

### mk_toolres

``` python

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`.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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 }
```

</details>

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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 res
```

</details>

``` python
tr = mk_toolres(r, ns=ns)
tr
```

    Finding 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'}]

``` python
m2 = msgs + tr
```

``` python
res = c(mk_msgs(m2), sp=sysp, tools=tools)
res
```

604542 + 6,458,932 = 7,063,474

<details>

- 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

</details>

``` python
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:

``` python
msgs = mk_toolres("I'm Jeremy")
c(msgs, sp=sysp, tools=tools, **rkw)
```

Nice to meet you, Jeremy. How can I help you today?

<details>

- 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

</details>

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L218"
target="_blank" style="float:right; font-size:smaller">source</a>

### Client.structured

``` python

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)*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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())
```

</details>

``` python
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()
```

``` python
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

``` python
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):

``` python
for o in r: print(o, end='')
```

After streaming is complete, `value.output` will contain the tool calls:

``` python
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:

``` python
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

<details>

- 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

</details>

## Chat

### Basic chat

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L229"
target="_blank" style="float:right; font-size:smaller">source</a>

### Chat

``` python

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.*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
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.use
```

</details>

``` python
chat = Chat(model, sp=sysp, **rkw)
chat.c.use, chat.h
```

    (In: 0; Out: 0; Total: 0, [])

------------------------------------------------------------------------

<a
href="https://github.com/AnswerDotAI/cosette/blob/main/cosette/core.py#L252"
target="_blank" style="float:right; font-size:smaller">source</a>

### Chat.\_\_call\_\_

``` python

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*

<details open class="code-fold">
<summary>Exported source</summary>

``` python
@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 res
```

</details>

``` python
chat("I'm Jeremy")
chat("What's my name?")
```

You said your name is Jeremy.

<details>

- 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

</details>

``` python
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?

``` python
r = chat("What's my name?", stream=True, **rkw)
for o in r: print(o, end='')
```

    You told me your name is Jeremy.

``` python
r.value
```

You told me your name is Jeremy.

<details>

- 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

</details>

History is stored in the `h` attr:

``` python
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

``` python
pr = f"What is {a}+{b}?"
pr
```

    'What is 604542+6458932?'

``` python
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')]

``` python
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`](https://AnswerDotAI.github.io/cosette/core.html#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`](https://AnswerDotAI.github.io/cosette/core.html#chat) ensures
only explicitly allowed tools can be executed.

``` python
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')]

``` python
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.

``` python
q = "In brief, what color flowers are in this image?"
chat([img, q])
```

The flowers are purple.

<details>

- 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

</details>

## Third Party Providers

### Azure OpenAI Service

Example Azure usage:

``` python
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:

``` python
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

<details>

- 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

</details>

Here’s an example of using the library with Groq:

``` python
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?")
```

``` python
gchat = Chat(cli=groq_c)
gchat("Hello! I'm Jeremy")
```

``` python
gchat("What's my name?")
```