OpenAI PowerShell Function Calls

Short note of how OpenAI can be used with Tools (similar to Llama tools)

How it works

Side by side with prompt, we are going to pass tools definition in jsonschema format

So if usual request looks like this:

$res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
    model             = "gpt-4o"
    input             = @(
      @{
        role    = "system"
        content = @(
          @{
            type = "input_text"
            text = "You are assistant"
          }
        )
      },
      @{
        role    = "user"
        content = @(
          @{
            type = "input_text"
            text = "Hello"
          }
        )
      }
    )
    text              = @{
      format = @{
        type = "text"
      }
    }
    temperature       = 1
    max_output_tokens = 2048
    top_p             = 1
    store             = $true
  })

$res.output | ConvertTo-Json -Depth 100

and response is:

{
  "type": "message",
  "id": "msg_67dd6e5b3bc08192ae7416b7916c587a034d4d1feea88755",
  "status": "completed",
  "role": "assistant",
  "content": [
    {
      "type": "output_text",
      "text": "Hi there! How can I assist you today?",
      "annotations": []
    }
  ]
}

All we need to do is to pass tools definitions like so:

$res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
    model             = "gpt-4o"
    input             = @(
      @{
        role    = "system"
        content = @(
          @{
            type = "input_text"
            text = "You are weather assistant"
          }
        )
      },
      @{
        role    = "user"
        content = @(
          @{
            type = "input_text"
            text = "What is the weather in Kiev?"
          }
        )
      }
    )
    text              = @{
      format = @{
        type = "text"
      }
    }
    tools             = @(
      @{
        type        = "function"
        name        = "get_weather"
        description = "Determine weather in my location"
        parameters  = @{
          type                 = "object"
          properties           = @{
            location = @{
              type        = "string"
              description = "The city and state e.g. San Francisco, CA"
            }
            unit     = @{
              type = "string"
              enum = @("c", "f")
            }
          }
          additionalProperties = $false
          required             = @("location", "unit")
        }
        strict      = $true
      }
    )
    temperature       = 1
    max_output_tokens = 2048
    top_p             = 1
    store             = $true
  })

$res.output | ConvertTo-Json -Depth 100

and output will be:

{
  "type": "function_call",
  "id": "fc_67dd6eeb077c8192ae4b5cb3ea34879e0c5ae11b9c2089fb",
  "call_id": "call_M6vaQLRWeHenyuvLtsTgZhOK",
  "name": "get_weather",
  "arguments": "{\"unit\":\"c\",\"location\":\"Kiev, Ukraine\"}",
  "status": "completed"
}

Note how different it is, it is not an text message anymore, but rather function fall

It is our job to perform this call and pass result back to the model, so it can form the response

Lets pretend that weather api give us response:

{ "temperature": 5 }

We pass this back to the model so it can form human readable response like so:

$res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
    model             = "gpt-4o"
    input             = @(
      @{
        role    = "system"
        content = @(
          @{
            type = "input_text"
            text = "You are weather assistant"
          }
        )
      },
      @{
        role    = "user"
        content = @(
          @{
            type = "input_text"
            text = "What is the weather in Kiev?"
          }
        )
      },
      # append previous response as is
      @{
        type      = "function_call"
        id        = "fc_67dd6eeb077c8192ae4b5cb3ea34879e0c5ae11b9c2089fb"
        call_id   = "call_M6vaQLRWeHenyuvLtsTgZhOK"
        name      = "get_weather"
        arguments = "{`"unit`":`"c`",`"location`":`"Kiev, Ukraine`"}"
        status    = "completed"
      },
      # as well as function call response
      @{
        type    = "function_call_output"
        call_id = "call_M6vaQLRWeHenyuvLtsTgZhOK" # keep track of the call_id
        output  = "{`"temperature`":5}"
      }
    )
    text              = @{
      format = @{
        type = "text"
      }
    }
    tools             = @(
      @{
        type        = "function"
        name        = "get_weather"
        description = "Determine weather in my location"
        parameters  = @{
          type                 = "object"
          properties           = @{
            location = @{
              type        = "string"
              description = "The city and state e.g. San Francisco, CA"
            }
            unit     = @{
              type = "string"
              enum = @("c", "f")
            }
          }
          additionalProperties = $false
          required             = @("location", "unit")
        }
        strict      = $true
      }
    )
    temperature       = 1
    max_output_tokens = 2048
    top_p             = 1
    store             = $true
  })

$res.output | ConvertTo-Json -Depth 100

model will recognize conversation history, function call and its response, and form following:

{
  "type": "message",
  "id": "msg_67dd705b92ec8192a1f0149080dda4e50c5ae11b9c2089fb",
  "status": "completed",
  "role": "assistant",
  "content": [
    {
      "type": "output_text",
      "text": "The current temperature in Kiev, Ukraine is 5°C.",
      "annotations": []
    }
  ]
}

MacOS manage Music and Volume with OpenAI via PowerShell

As example, lets built an bot that will manage MacOS Music and Volue

Our functions are:

get current volume level

osascript -e "output volume of (get volume settings)"

set volume level

osascript -e "set volume output volume 50"

mute

osascript -e "set volume with output muted"

unmute

osascript -e "set volume without output muted"

player state

osascript -e 'tell application "Music" to player state'

next song

osascript -e 'tell application "Music" to next track'

previous song

osascript -e 'tell application "Music" to previous track'

play/pause

osascript -e 'tell application "Music" to playpause'

stop

osascript -e 'tell application "Music" to stop'

get current song name

osascript -e 'tell application "Music" to name of current track'

Here is an example with tool definitions:

$res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
    model             = "gpt-4o"
    input             = @(
      @{
        role    = "system"
        content = @(
          @{
            type = "input_text"
            text = "You are home assistant capable of changing volume level and control music player"
          }
        )
      },
      @{
        role    = "user"
        content = @(
          @{
            type = "input_text"
            text = "What song is playing now?"
          }
        )
      }
    )
    text              = @{
      format = @{
        type = "text"
      }
    }
    tools             = @(
      @{
        type        = "function"
        name        = "mute_volume"
        description = "Mute volume"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "unmute_volume"
        description = "Unmute volume"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "get_volume_level"
        description = "Retrieve current volume level"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "set_volume_level"
        description = "Set volume level"
        parameters  = @{
          type                 = "object"
          properties           = @{
            level = @{
              type    = "number"
              minimum = 0
              maximum = 100
            }
          }
          additionalProperties = $false
          required             = @("level")
        }
      },
      @{
        type        = "function"
        name        = "get_player_state"
        description = "Retrieve state of the player, e.g. playing, paused, stopped"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "play_next_song"
        description = "Play next song"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "play_previous_song"
        description = "Play previous song"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "player_toggle_play_pause"
        description = "Play/pause toggler"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "player_stop"
        description = "Stop player"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "get_current_song_name"
        description = "Get current song name"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      }
    )
    temperature       = 1
    max_output_tokens = 2048
    top_p             = 1
    store             = $true
  })

$res.output | ConvertTo-Json -Depth 100

it will respond with:

{
  "type": "function_call",
  "id": "fc_67dd7920cf2c819298b39b3ff3e79c6d002defc744541390",
  "call_id": "call_52fbENg733i3fGx3x0hmirnn",
  "name": "get_current_song_name",
  "arguments": "{}",
  "status": "completed"
}

our job now is to call script:

$result = osascript -e 'tell application "Music" to name of current track'

and pass it back to the model

$res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
    model             = "gpt-4o"
    input             = @(
      @{
        role    = "system"
        content = @(
          @{
            type = "input_text"
            text = "You are home assistant capable of changing volume level and control music player"
          }
        )
      },
      @{
        role    = "user"
        content = @(
          @{
            type = "input_text"
            text = "What song is playing now?"
          }
        )
      },
      # pass previous response as is
      @{
        type      = "function_call"
        id        = "fc_67dd7920cf2c819298b39b3ff3e79c6d002defc744541390"
        call_id   = "call_52fbENg733i3fGx3x0hmirnn"
        name      = "get_current_song_name"
        arguments = "{}"
        status    = "completed"
      },
      # as well as function call response
      @{
        type    = "function_call_output"
        call_id = "call_52fbENg733i3fGx3x0hmirnn" # keep track of the call_id
        output  = "{`"song_name`":`"Maria (I Like It Loud)`"}"
      }
    )
    text              = @{
      format = @{
        type = "text"
      }
    }
    tools             = @(
      @{
        type        = "function"
        name        = "mute_volume"
        description = "Mute volume"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "unmute_volume"
        description = "Unmute volume"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "get_volume_level"
        description = "Retrieve current volume level"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "set_volume_level"
        description = "Set volume level"
        parameters  = @{
          type                 = "object"
          properties           = @{
            level = @{
              type    = "number"
              minimum = 0
              maximum = 100
            }
          }
          additionalProperties = $false
          required             = @("level")
        }
      },
      @{
        type        = "function"
        name        = "get_player_state"
        description = "Retrieve state of the player, e.g. playing, paused, stopped"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "play_next_song"
        description = "Play next song"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "play_previous_song"
        description = "Play previous song"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "player_toggle_play_pause"
        description = "Play/pause toggler"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "player_stop"
        description = "Stop player"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      },
      @{
        type        = "function"
        name        = "get_current_song_name"
        description = "Get current song name"
        parameters  = @{
          type                 = "object"
          properties           = @{}
          required             = @()
          additionalProperties = $false
        }
      }
    )
    temperature       = 1
    max_output_tokens = 2048
    top_p             = 1
    store             = $true
  })

$res.output | ConvertTo-Json -Depth 100

response will be something like:

{
  "type": "message",
  "id": "msg_67dd79c35a24819298f019f942b5c1ce002defc744541390",
  "status": "completed",
  "role": "assistant",
  "content": [
    {
      "type": "output_text",
      "text": "The current song playing is \"Maria (I Like It Loud).\"",
      "annotations": []
    }
  ]
}

and now it is time to combine everything together

$tools = @(
  @{
    type        = "function"
    name        = "mute_volume"
    description = "Mute volume"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "unmute_volume"
    description = "Unmute volume"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "get_volume_level"
    description = "Retrieve current volume level"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "set_volume_level"
    description = "Set volume level"
    parameters  = @{
      type                 = "object"
      properties           = @{
        level = @{
          type    = "number"
          minimum = 0
          maximum = 100
        }
      }
      additionalProperties = $false
      required             = @("level")
    }
  },
  @{
    type        = "function"
    name        = "get_player_state"
    description = "Retrieve state of the player, e.g. playing, paused, stopped"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "play_next_song"
    description = "Play next song"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "play_previous_song"
    description = "Play previous song"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "player_toggle_play_pause"
    description = "Play/pause toggler"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "player_stop"
    description = "Stop player"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  },
  @{
    type        = "function"
    name        = "get_current_song_name"
    description = "Get current song name"
    parameters  = @{
      type                 = "object"
      properties           = @{}
      required             = @()
      additionalProperties = $false
    }
  }
)

$conversation = @(
  @{
    role    = "system"
    content = @(
      @{
        type = "input_text"
        text = "You are home assistant capable of changing volume level and control music player"
      }
    )
  }
)

$prompt = $true
while ($true) {
  if ($prompt) {
    $message = Read-Host "Enter message"
    if (-not $message -or $message.ToLower() -eq "exit" -or $message.ToLower() -eq "quit") {
      Write-Host "Bye!"
      break
    }

    $conversation += @{
      role    = "user"
      content = $message
    }
    $prompt = $false
  }

  $res = Invoke-RestMethod -Method Post -Uri "https://api.openai.com/v1/responses" -Headers @{"Content-Type" = "application/json"; "Authorization" = "Bearer $($env:OPENAI_API_KEY)" } -Body (ConvertTo-Json -Depth 100 -InputObject @{
      model             = "gpt-4o"
      input             = $conversation
      text              = @{
        format = @{
          type = "text"
        }
      }
      tools             = $tools
      temperature       = 1
      max_output_tokens = 2048
      top_p             = 1
      store             = $true
    })

  $conversation += $res.output

  if ($res.output.type -eq "message") {
    Write-Host $res.output.content[0].text
    $prompt = $true
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "mute_volume") {
    osascript -e "set volume with output muted"
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "unmute_volume") {
    osascript -e "set volume without output muted"
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "get_volume_level") {
    $result = [int](osascript -e "output volume of (get volume settings)")
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"level`":$result}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "set_volume_level") {
    $level = $res.output.arguments | ConvertFrom-Json | Select-Object -ExpandProperty level
    osascript -e "set volume output volume $level"
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "get_player_state") {
    $result = osascript -e 'tell application "Music" to player state'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"state`":`"$result`"}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "play_next_song") {
    osascript -e 'tell application "Music" to next track'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "play_previous_song") {
    osascript -e 'tell application "Music" to previous track'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "player_toggle_play_pause") {
    osascript -e 'tell application "Music" to playpause'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "player_stop") {
    osascript -e 'tell application "Music" to stop'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = "{`"success`":true}"
    }
  }
  elseif ($res.output.type -eq "function_call" -and $res.output.name -eq "get_current_song_name") {
    $result = osascript -e 'tell application "Music" to name of current track'
    $conversation += @{
      type    = "function_call_output"
      call_id = $res.output.call_id
      output  = $result
    }
  }
}

And here is transcript:

Enter message: is music playing?
No, the music is currently stopped.
Enter message: start it
The music has been started.
Enter message: make it louder
I've increased the volume to 35%.
Enter message: play next song
The next song is now playing.
Enter message: what song is that?
The current song playing is "Statement."

And technically you can do almost anything with such approach, and imagine what can be done if you add text to speech on top

To see, what other osascript script available open Script Editor.app and from File menu choose Open Dictionary... - popup with all possible and impossible applications will be shown, just choose one you want to play with and scrip editor will show you what's available.