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.