Skip to content

Commit 61c7765

Browse files
committed
Split alarms and timers completely.
Signed-off-by: Katharine Berry <[email protected]>
1 parent 2096e2e commit 61c7765

File tree

3 files changed

+184
-69
lines changed

3 files changed

+184
-69
lines changed

service/assistant/functions/alarms.go

Lines changed: 176 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -26,58 +26,49 @@ import (
2626
)
2727

2828
type AlarmInput struct {
29-
// If setting an alarm, the time to schedule the alarm for in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'. Required for alarms. Must always be in the future.
29+
// The time to schedule the alarm for in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'. Must always be in the future.
3030
Time string `json:"time"`
31+
// An optional name for the alarm.
32+
Name string `json:"name"`
33+
}
34+
35+
type TimerInput struct {
3136
// If setting a timer, the number of seconds to set the timer for. Required for timers.
3237
Duration int `json:"duration_seconds"`
33-
// True if this is a timer, false if it's an alarm.
34-
IsTimer bool `json:"is_timer" jsonschema:"required"`
3538
// An optional name for the alarm or timer.
3639
Name string `json:"name"`
3740
}
3841

3942
type DeleteAlarmInput struct {
40-
// The time of the alarm or timer to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.
43+
// The time of the alarm to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.
4144
Time string `json:"time"`
42-
// True if deleting a timer, false if deleting an alarm.
43-
IsTimer bool `json:"is_timer"`
4445
}
4546

46-
type GetAlarmInput struct {
47-
// True if retrieving timers, false if returning alarms.
48-
IsTimer bool `json:"is_timer" jsonschema:"required"`
47+
type DeleteTimerInput struct {
48+
// The expiration time of the timer to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.
49+
Time string `json:"time"`
4950
}
5051

52+
type Empty struct{}
53+
5154
func init() {
5255
params := genai.Schema{
5356
Type: genai.TypeObject,
5457
Nullable: false,
5558
Properties: map[string]*genai.Schema{
5659
"time": {
5760
Type: genai.TypeString,
58-
Description: "If setting an alarm, the time to schedule the alarm for in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'. Required for alarms. Must always be in the future.",
61+
Description: "The time to schedule the alarm for in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'. Must always be in the future.",
5962
Nullable: true,
6063
},
61-
"duration_seconds": {
62-
Type: genai.TypeInteger,
63-
Description: "If setting a timer, the number of seconds to set the timer for. Required for timers.",
64-
Nullable: true,
65-
Format: "int32",
66-
},
67-
"is_timer": {
68-
Type: genai.TypeBoolean,
69-
Description: "True if this is a timer, false if it's an alarm.",
70-
Nullable: false,
71-
},
7264
},
73-
Required: []string{"is_timer"},
7465
}
7566
// This registration is for old watch apps that don't support named alarms. The anticapability prevents it
7667
// from being seen by newer apps.
7768
registerFunction(Registration{
7869
Definition: genai.FunctionDeclaration{
7970
Name: "set_alarm",
80-
Description: "Get or set an alarm or a timer for a given time.",
71+
Description: "Set an alarm for a given time.",
8172
Parameters: &params,
8273
},
8374
Cb: alarmImpl,
@@ -90,15 +81,15 @@ func init() {
9081
paramsWithNames.Properties = maps.Clone(params.Properties)
9182
paramsWithNames.Properties["name"] = &genai.Schema{
9283
Type: genai.TypeString,
93-
Description: "Only if explicitly specified by the user, the name of the alarm or timer. Use title case. If the user didn't ask to name the timer, just leave it empty.",
84+
Description: "Only if explicitly specified by the user, the name of the alarm. Use title case. If the user didn't ask to name the alarm, just leave it empty.",
9485
Nullable: true,
9586
}
9687
// This registration is for new watch apps that support named alarms. The capability prevents the option for
9788
// naming being presented to the model for older apps.
9889
registerFunction(Registration{
9990
Definition: genai.FunctionDeclaration{
10091
Name: "set_alarm",
101-
Description: "Get or set an alarm or a timer for a given time.",
92+
Description: "Set an alarm for a given time.",
10293
Parameters: &paramsWithNames,
10394
},
10495
Cb: alarmImpl,
@@ -110,65 +101,142 @@ func init() {
110101
registerFunction(Registration{
111102
Definition: genai.FunctionDeclaration{
112103
Name: "get_alarms",
113-
Description: "Get any existing alarms or timers. **There is no get_timers, call this with is_timer=true instead.**",
104+
Description: "Get any existing alarms.",
105+
},
106+
Aliases: []string{"get_alarm"},
107+
Cb: getAlarmImpl,
108+
Thought: getAlarmThought,
109+
InputType: Empty{},
110+
})
111+
112+
registerFunction(Registration{
113+
Definition: genai.FunctionDeclaration{
114+
Name: "delete_alarm",
115+
Description: "Delete a specific alarm by its expiration time.",
114116
Parameters: &genai.Schema{
115117
Type: genai.TypeObject,
116118
Nullable: false,
117119
Properties: map[string]*genai.Schema{
118-
"is_timer": {
119-
Type: genai.TypeBoolean,
120-
Description: "True if retrieving timers, false if returning alarms.",
120+
"time": {
121+
Type: genai.TypeString,
122+
Description: "The time of the alarm to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.",
121123
Nullable: false,
122124
},
123125
},
124-
Required: []string{"is_timer"},
125126
},
126127
},
127-
Aliases: []string{"get_alarms", "get_timer", "get_timers"},
128-
Cb: getAlarmImpl,
129-
Thought: getAlarmThought,
130-
InputType: GetAlarmInput{},
128+
Cb: deleteAlarmImpl,
129+
Thought: deleteAlarmThought,
130+
InputType: DeleteAlarmInput{},
131+
})
132+
timerParams := genai.Schema{
133+
Type: genai.TypeObject,
134+
Nullable: false,
135+
Properties: map[string]*genai.Schema{
136+
"duration_seconds": {
137+
Type: genai.TypeInteger,
138+
Description: "The number of seconds to set the timer for.",
139+
Nullable: true,
140+
Format: "int32",
141+
},
142+
},
143+
}
144+
// This registration is for old watch apps that don't support named alarms. The anticapability prevents it
145+
// from being seen by newer apps.
146+
registerFunction(Registration{
147+
Definition: genai.FunctionDeclaration{
148+
Name: "set_timer",
149+
Description: "Set a timer for a given duration.",
150+
Parameters: &timerParams,
151+
},
152+
Cb: timerImpl,
153+
Thought: timerThought,
154+
InputType: TimerInput{},
155+
AntiCapability: "named_alarms",
156+
})
157+
timerParamsWithNames := timerParams
158+
timerParamsWithNames.Properties = maps.Clone(timerParams.Properties)
159+
timerParamsWithNames.Properties["name"] = &genai.Schema{
160+
Type: genai.TypeString,
161+
Description: "Only if explicitly specified by the user, the name of the timer. Use title case. If the user didn't ask to name the timer, just leave it empty.",
162+
Nullable: true,
163+
}
164+
registerFunction(Registration{
165+
Definition: genai.FunctionDeclaration{
166+
Name: "set_timer",
167+
Description: "Set a timer for a given time.",
168+
Parameters: &timerParamsWithNames,
169+
},
170+
Cb: timerImpl,
171+
Thought: timerThought,
172+
InputType: TimerInput{},
173+
Capability: "named_alarms",
131174
})
132175

133176
registerFunction(Registration{
134177
Definition: genai.FunctionDeclaration{
135-
Name: "delete_alarm",
136-
Description: "Delete a specific alarm or timer by its expiration time. When deleting a timer, you must call get_alarm first to get the expiration time (calculating it from the chat history will only be approximate)",
178+
Name: "get_timers",
179+
Description: "Get any existing timers.",
180+
},
181+
Aliases: []string{"get_timer"},
182+
Cb: getTimerImpl,
183+
Thought: getTimerThought,
184+
InputType: Empty{},
185+
})
186+
187+
registerFunction(Registration{
188+
Definition: genai.FunctionDeclaration{
189+
Name: "delete_timer",
190+
Description: "Delete a specific timer by its expiration time.",
137191
Parameters: &genai.Schema{
138192
Type: genai.TypeObject,
139193
Nullable: false,
140194
Properties: map[string]*genai.Schema{
141195
"time": {
142196
Type: genai.TypeString,
143-
Description: "The time of the alarm or timer to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.",
144-
Nullable: false,
145-
},
146-
"is_timer": {
147-
Type: genai.TypeBoolean,
148-
Description: "True if deleting a timer, false if deleting an alarm.",
197+
Description: "The time of the alarm to delete in ISO 8601 format, e.g. '2023-07-12T00:00:00-07:00'.",
149198
Nullable: false,
150199
},
151200
},
152201
},
153202
},
154-
Cb: deleteAlarmImpl,
155-
Thought: deleteAlarmThought,
156-
InputType: DeleteAlarmInput{},
203+
Cb: deleteTimerImpl,
204+
Thought: deleteTimerThought,
205+
InputType: DeleteTimerInput{},
157206
})
158207
}
159208

160209
func alarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
161210
ctx, span := beeline.StartSpan(ctx, "set_alarm")
162211
defer span.Send()
163212
if !query.SupportsAction(ctx, "set_alarm") {
164-
return Error{Error: "You need to update the app on your watch to set alarms or timers."}
213+
return Error{Error: "You need to update the app on your watch to set alarms."}
165214
}
166215
input := args.(*AlarmInput)
167216
log.Println("Asking watch to set an alarm...")
168217
requests <- map[string]interface{}{
169-
"time": input.Time,
218+
"time": input.Time,
219+
"isTimer": false,
220+
"name": input.Name,
221+
"action": "set_alarm",
222+
"cancel": false,
223+
}
224+
log.Println("Waiting for confirmation...")
225+
resp := <-responses
226+
return resp
227+
}
228+
229+
func timerImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
230+
ctx, span := beeline.StartSpan(ctx, "set_timer")
231+
defer span.Send()
232+
if !query.SupportsAction(ctx, "set_alarm") {
233+
return Error{Error: "You need to update the app on your watch to set timers."}
234+
}
235+
input := args.(*TimerInput)
236+
log.Println("Asking watch to set an alarm...")
237+
requests <- map[string]interface{}{
170238
"duration": input.Duration,
171-
"isTimer": input.IsTimer,
239+
"isTimer": true,
172240
"name": input.Name,
173241
"action": "set_alarm",
174242
"cancel": false,
@@ -180,26 +248,33 @@ func alarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{
180248

181249
func alarmThought(i interface{}) string {
182250
args := i.(*AlarmInput)
183-
if args.IsTimer {
184-
return "Setting a timer"
185-
} else if args.Time == "" {
251+
if args.Time == "" {
186252
return "Contemplating time"
187253
} else {
188254
return "Setting an alarm"
189255
}
190256
}
191257

258+
func timerThought(i interface{}) string {
259+
args := i.(*TimerInput)
260+
if args.Duration == 0 {
261+
return "Contemplating time"
262+
} else {
263+
return "Setting a timer"
264+
}
265+
}
266+
192267
func deleteAlarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
193-
ctx, span := beeline.StartSpan(ctx, "set_alarm")
268+
ctx, span := beeline.StartSpan(ctx, "delete_alarm")
194269
defer span.Send()
195270
if !query.SupportsAction(ctx, "set_alarm") {
196-
return Error{Error: "You need to update the app on your watch to delete alarms or timers."}
271+
return Error{Error: "You need to update the app on your watch to delete alarms."}
197272
}
198273
input := args.(*DeleteAlarmInput)
199274
log.Printf("Asking watch to delete an alarm set for %s...\n", input.Time)
200275
requests <- map[string]interface{}{
201276
"time": input.Time,
202-
"isTimer": input.IsTimer,
277+
"isTimer": false,
203278
"action": "set_alarm",
204279
"cancel": true,
205280
}
@@ -208,25 +283,42 @@ func deleteAlarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args inte
208283
return resp
209284
}
210285

211-
func deleteAlarmThought(i interface{}) string {
212-
args := i.(*DeleteAlarmInput)
213-
if args.IsTimer {
214-
return "Deleting a timer"
215-
} else {
216-
return "Deleting an alarm"
286+
func deleteTimerImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
287+
ctx, span := beeline.StartSpan(ctx, "delete_timer")
288+
defer span.Send()
289+
if !query.SupportsAction(ctx, "set_alarm") {
290+
return Error{Error: "You need to update the app on your watch to delete timers."}
217291
}
292+
input := args.(*DeleteTimerInput)
293+
log.Printf("Asking watch to delete a timer set for %s...\n", input.Time)
294+
requests <- map[string]interface{}{
295+
"time": input.Time,
296+
"isTimer": true,
297+
"action": "set_alarm",
298+
"cancel": true,
299+
}
300+
log.Println("Waiting for confirmation...")
301+
resp := <-responses
302+
return resp
303+
}
304+
305+
func deleteAlarmThought(i interface{}) string {
306+
return "Deleting an alarm"
307+
}
308+
309+
func deleteTimerThought(i interface{}) string {
310+
return "Deleting a timer"
218311
}
219312

220313
func getAlarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
221314
ctx, span := beeline.StartSpan(ctx, "get_alarm")
222315
defer span.Send()
223316
if !query.SupportsAction(ctx, "get_alarm") {
224-
return Error{Error: "You need to update the app on your watch to get alarms or timers."}
317+
return Error{Error: "You need to update the app on your watch to get alarms."}
225318
}
226-
input := args.(*GetAlarmInput)
227319
log.Println("Asking watch to get alarms...")
228320
requests <- map[string]interface{}{
229-
"isTimer": input.IsTimer,
321+
"isTimer": false,
230322
"action": "get_alarm",
231323
}
232324
log.Println("Waiting for response...")
@@ -235,10 +327,27 @@ func getAlarmImpl(ctx context.Context, quotaTracker *quota.Tracker, args interfa
235327
return resp
236328
}
237329

238-
func getAlarmThought(i interface{}) string {
239-
args := i.(*GetAlarmInput)
240-
if args.IsTimer {
241-
return "Checking your timers"
330+
func getTimerImpl(ctx context.Context, quotaTracker *quota.Tracker, args interface{}, requests chan<- map[string]interface{}, responses <-chan map[string]interface{}) interface{} {
331+
ctx, span := beeline.StartSpan(ctx, "get_timer")
332+
defer span.Send()
333+
if !query.SupportsAction(ctx, "get_alarm") {
334+
return Error{Error: "You need to update the app on your watch to get timers."}
242335
}
336+
log.Println("Asking watch to get alarms...")
337+
requests <- map[string]interface{}{
338+
"isTimer": true,
339+
"action": "get_alarm",
340+
}
341+
log.Println("Waiting for response...")
342+
resp := <-responses
343+
log.Println("Got response:", resp)
344+
return resp
345+
}
346+
347+
func getAlarmThought(i interface{}) string {
243348
return "Checking your alarms"
244349
}
350+
351+
func getTimerThought(i interface{}) string {
352+
return "Checking your timers"
353+
}

service/assistant/system_prompt.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (ps *PromptSession) generateSystemPrompt(ctx context.Context) string {
119119
"As a creative, intelligent, helpful, friendly assistant, you should always try to answer the user's question. You can and should provide creative suggestions and factual responses as appropriate. Always try your best to answer the user's question. " +
120120
"**Never** claim to have taken an action (e.g. set a timer, alarm, or reminder) unless you have actually used a tool to do so. " +
121121
"Even if in previous turns you have apparently taken an action (like setting an alarm) without using a tool, you must still use tools if asked to do so again. " +
122-
"Alarms and reminders are not interchangable - *never* use alarms when a user asks for reminders, and never user reminders when the user asks for an alarm or timer. If a user asks to set a timer, always set a timer (using 'set_alarm'). If the user asks about a specific timer, respond only about that one. " +
122+
"Alarms and reminders are not interchangable - *never* use alarms when a user asks for reminders, and never user reminders when the user asks for an alarm or timer. If a user asks to set a timer, always set a timer (using 'set_timer'), not a reminder. If the user asks about a specific timer, respond only about that one. " +
123123
"Your responses will be displayed on a very small screen, so be brief. Do not use markdown in your responses.\n" +
124124
//"If asked to perform a calculation, YOU MUST ALWAYS respond with the answer. The user cannot see the results of calling functions automatically.\n" +
125125
generateWidgetSentence(ctx) +

0 commit comments

Comments
 (0)