@guava-ai/guava-sdk 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/example-runner.js +16 -7
- package/dist/examples/credit-card-activation.js +94 -112
- package/dist/examples/credit-card-activation.js.map +1 -1
- package/dist/examples/property-insurance.js +12 -26
- package/dist/examples/property-insurance.js.map +1 -1
- package/dist/examples/scheduling-outbound.d.ts +1 -0
- package/dist/examples/scheduling-outbound.js +62 -0
- package/dist/examples/scheduling-outbound.js.map +1 -0
- package/dist/examples/thai-palace.js +25 -43
- package/dist/examples/thai-palace.js.map +1 -1
- package/dist/package.json +7 -5
- package/dist/src/action_item.d.ts +34 -12
- package/dist/src/action_item.js +34 -7
- package/dist/src/action_item.js.map +1 -1
- package/dist/src/call-controller.d.ts +137 -0
- package/dist/src/call-controller.js +433 -0
- package/dist/src/call-controller.js.map +1 -0
- package/dist/src/commands.d.ts +67 -27
- package/dist/src/commands.js +41 -27
- package/dist/src/commands.js.map +1 -1
- package/dist/src/events.d.ts +47 -30
- package/dist/src/events.js +42 -36
- package/dist/src/events.js.map +1 -1
- package/dist/src/example_data.d.ts +1 -0
- package/dist/src/example_data.js +33 -0
- package/dist/src/example_data.js.map +1 -1
- package/dist/src/helpers/openai.d.ts +12 -1
- package/dist/src/helpers/openai.js +168 -68
- package/dist/src/helpers/openai.js.map +1 -1
- package/dist/src/index.d.ts +6 -121
- package/dist/src/index.js +249 -483
- package/dist/src/index.js.map +1 -1
- package/dist/src/logging.d.ts +2 -1
- package/dist/src/logging.js +32 -7
- package/dist/src/logging.js.map +1 -1
- package/dist/src/telemetry.d.ts +23 -0
- package/dist/src/telemetry.js +98 -0
- package/dist/src/telemetry.js.map +1 -0
- package/dist/src/utils.d.ts +3 -0
- package/dist/src/utils.js +28 -0
- package/dist/src/utils.js.map +1 -0
- package/examples/biome.json +5 -0
- package/examples/credit-card-activation.ts +20 -26
- package/examples/property-insurance.ts +6 -16
- package/examples/scheduling-outbound.ts +80 -0
- package/examples/thai-palace.ts +10 -13
- package/package.json +7 -5
- package/src/action_item.ts +53 -13
- package/src/call-controller.ts +451 -0
- package/src/commands.ts +58 -42
- package/src/events.ts +66 -51
- package/src/example_data.ts +42 -0
- package/src/helpers/openai.ts +73 -18
- package/src/index.ts +81 -403
- package/src/logging.ts +39 -7
- package/src/telemetry.ts +125 -0
- package/src/utils.ts +32 -0
package/src/commands.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { ActionItem } from "./action_item.ts";
|
|
3
3
|
|
|
4
|
-
export const
|
|
4
|
+
export const StartOutboundCallCommand = z.strictObject({
|
|
5
5
|
command_type: z.literal("start-outbound"),
|
|
6
6
|
|
|
7
7
|
from_number: z.e164().optional(),
|
|
8
8
|
to_number: z.e164(),
|
|
9
9
|
});
|
|
10
|
-
export type StartOutboundCallCommand = z.input<typeof
|
|
10
|
+
export type StartOutboundCallCommand = z.input<typeof StartOutboundCallCommand>;
|
|
11
11
|
|
|
12
|
-
export const
|
|
12
|
+
export const ListenInboundCommand = z
|
|
13
13
|
.strictObject({
|
|
14
14
|
command_type: z.literal("listen-inbound"),
|
|
15
15
|
|
|
@@ -18,83 +18,99 @@ export const listenInboundCommand = z
|
|
|
18
18
|
})
|
|
19
19
|
.refine(
|
|
20
20
|
(obj) => {
|
|
21
|
-
return
|
|
22
|
-
typeof obj.agent_number == "string" ||
|
|
23
|
-
typeof obj.webrtc_code == "string"
|
|
24
|
-
);
|
|
21
|
+
return typeof obj.agent_number === "string" || typeof obj.webrtc_code === "string";
|
|
25
22
|
},
|
|
26
23
|
{ error: "one of ['agent_number', 'webrtc_code'] must be set" },
|
|
27
24
|
);
|
|
28
|
-
export type ListenInboundCommand = z.input<typeof
|
|
25
|
+
export type ListenInboundCommand = z.input<typeof ListenInboundCommand>;
|
|
29
26
|
|
|
30
|
-
export const
|
|
27
|
+
export const RejectInboundCallCommand = z.strictObject({
|
|
31
28
|
command_type: z.literal("reject-inbound"),
|
|
32
29
|
});
|
|
33
|
-
export type RejectInboundCallCommand = z.input<typeof
|
|
30
|
+
export type RejectInboundCallCommand = z.input<typeof RejectInboundCallCommand>;
|
|
34
31
|
|
|
35
|
-
export const
|
|
32
|
+
export const AcceptInboundCallCommand = z.strictObject({
|
|
36
33
|
command_type: z.literal("accept-inbound"),
|
|
37
34
|
});
|
|
38
|
-
export type AcceptInboundCallCommand = z.input<typeof
|
|
35
|
+
export type AcceptInboundCallCommand = z.input<typeof AcceptInboundCallCommand>;
|
|
39
36
|
|
|
40
|
-
export const
|
|
37
|
+
export const SetTaskCommand = z.strictObject({
|
|
41
38
|
command_type: z.literal("set-task"),
|
|
42
39
|
task_id: z.string(),
|
|
43
40
|
objective: z.string(),
|
|
44
|
-
action_items: z.array(
|
|
41
|
+
action_items: z.array(ActionItem),
|
|
45
42
|
});
|
|
46
|
-
export type SetTaskCommand = z.input<typeof
|
|
43
|
+
export type SetTaskCommand = z.input<typeof SetTaskCommand>;
|
|
47
44
|
|
|
48
|
-
export const
|
|
45
|
+
export const ReadScriptCommand = z.strictObject({
|
|
49
46
|
command_type: z.literal("read-script"),
|
|
50
47
|
script: z.string(),
|
|
51
48
|
});
|
|
52
|
-
export type ReadScriptCommand = z.input<typeof
|
|
49
|
+
export type ReadScriptCommand = z.input<typeof ReadScriptCommand>;
|
|
53
50
|
|
|
54
|
-
export const
|
|
51
|
+
export const AnswerQuestionCommand = z.strictObject({
|
|
55
52
|
command_type: z.literal("answer-question"),
|
|
56
53
|
question_id: z.string(),
|
|
57
54
|
answer: z.string(),
|
|
58
55
|
});
|
|
59
|
-
export type AnswerQuestionCommand = z.input<typeof
|
|
56
|
+
export type AnswerQuestionCommand = z.input<typeof AnswerQuestionCommand>;
|
|
60
57
|
|
|
61
|
-
export const
|
|
58
|
+
export const SetPersona = z.strictObject({
|
|
62
59
|
command_type: z.literal("set-persona"),
|
|
63
60
|
agent_name: z.string().optional(),
|
|
64
61
|
organization_name: z.string().optional(),
|
|
65
62
|
agent_purpose: z.string().optional(),
|
|
63
|
+
voice: z.string().optional(),
|
|
66
64
|
});
|
|
67
|
-
export type SetPersona = z.input<typeof
|
|
65
|
+
export type SetPersona = z.input<typeof SetPersona>;
|
|
68
66
|
|
|
69
|
-
export const
|
|
67
|
+
export const SendInstructionCommand = z.strictObject({
|
|
70
68
|
command_type: z.literal("send-instruction"),
|
|
71
69
|
instruction: z.string(),
|
|
72
70
|
});
|
|
73
|
-
export type SendInstructionCommand = z.input<typeof
|
|
71
|
+
export type SendInstructionCommand = z.input<typeof SendInstructionCommand>;
|
|
74
72
|
|
|
75
|
-
export const
|
|
73
|
+
export const TransferCommand = z.strictObject({
|
|
76
74
|
command_type: z.literal("transfer-call"),
|
|
77
75
|
transfer_message: z.string(),
|
|
78
76
|
to_number: z.string(),
|
|
79
77
|
});
|
|
80
|
-
export type TransferCommand = z.input<typeof
|
|
81
|
-
|
|
82
|
-
export const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
78
|
+
export type TransferCommand = z.input<typeof TransferCommand>;
|
|
79
|
+
|
|
80
|
+
export const RegisteredHooksCommand = z.strictObject({
|
|
81
|
+
command_type: z.literal("registered-hooks"),
|
|
82
|
+
has_on_question: z.boolean(),
|
|
83
|
+
has_on_intent: z.boolean(),
|
|
84
|
+
});
|
|
85
|
+
export type RegisteredHooksCommand = z.input<typeof RegisteredHooksCommand>;
|
|
86
|
+
|
|
87
|
+
export const ChoiceResultCommand = z.strictObject({
|
|
88
|
+
command_type: z.literal("choice-query-result"),
|
|
89
|
+
field_key: z.string(),
|
|
90
|
+
query_id: z.string(),
|
|
91
|
+
matched_choices: z.array(z.string()),
|
|
92
|
+
other_choices: z.array(z.string()),
|
|
93
|
+
});
|
|
94
|
+
export type ChoiceResultCommand = z.input<typeof ChoiceResultCommand>;
|
|
95
|
+
|
|
96
|
+
export const AnyCommand = z.union([
|
|
97
|
+
StartOutboundCallCommand,
|
|
98
|
+
ListenInboundCommand,
|
|
99
|
+
RejectInboundCallCommand,
|
|
100
|
+
AcceptInboundCallCommand,
|
|
101
|
+
SetTaskCommand,
|
|
102
|
+
ReadScriptCommand,
|
|
103
|
+
AnswerQuestionCommand,
|
|
104
|
+
SetPersona,
|
|
105
|
+
SendInstructionCommand,
|
|
106
|
+
TransferCommand,
|
|
107
|
+
RegisteredHooksCommand,
|
|
108
|
+
ChoiceResultCommand,
|
|
93
109
|
]);
|
|
94
|
-
export type Command = z.input<typeof
|
|
110
|
+
export type Command = z.input<typeof AnyCommand>;
|
|
95
111
|
|
|
96
|
-
export const
|
|
112
|
+
export const InboundTunnelCommand = z.strictObject({
|
|
97
113
|
call_id: z.string(),
|
|
98
|
-
command:
|
|
114
|
+
command: AnyCommand,
|
|
99
115
|
});
|
|
100
|
-
export type InboundTunnelCommand = z.input<typeof
|
|
116
|
+
export type InboundTunnelCommand = z.input<typeof InboundTunnelCommand>;
|
package/src/events.ts
CHANGED
|
@@ -1,123 +1,136 @@
|
|
|
1
1
|
import * as z from "zod";
|
|
2
2
|
|
|
3
|
-
export const
|
|
3
|
+
export const SessionStartedEvent = z.object({
|
|
4
4
|
event_type: z.literal("session-started"),
|
|
5
5
|
session_id: z.string(),
|
|
6
6
|
});
|
|
7
|
-
export type SessionStartedEvent = z.
|
|
7
|
+
export type SessionStartedEvent = z.infer<typeof SessionStartedEvent>;
|
|
8
8
|
|
|
9
|
-
export const
|
|
9
|
+
export const InboundCallEvent = z.object({
|
|
10
10
|
event_type: z.literal("inbound-call"),
|
|
11
11
|
caller_number: z.e164().optional(),
|
|
12
12
|
agent_number: z.e164().optional(),
|
|
13
13
|
});
|
|
14
|
-
export type InboundCallEvent = z.
|
|
14
|
+
export type InboundCallEvent = z.infer<typeof InboundCallEvent>;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @description The caller has said something.
|
|
18
18
|
*/
|
|
19
|
-
export const
|
|
19
|
+
export const CallerSpeechEvent = z.object({
|
|
20
20
|
event_type: z.literal("caller-speech"),
|
|
21
21
|
|
|
22
22
|
utterance: z.string(),
|
|
23
23
|
});
|
|
24
|
-
export type CallerSpeechEvent = z.
|
|
24
|
+
export type CallerSpeechEvent = z.infer<typeof CallerSpeechEvent>;
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* @description The agent has said something.
|
|
28
28
|
*/
|
|
29
|
-
export const
|
|
29
|
+
export const AgentSpeechEvent = z.object({
|
|
30
30
|
event_type: z.literal("agent-speech"),
|
|
31
31
|
|
|
32
32
|
utterance: z.string(),
|
|
33
33
|
interrupted: z.boolean().default(false),
|
|
34
34
|
});
|
|
35
|
-
export type AgentSpeechEvent = z.
|
|
35
|
+
export type AgentSpeechEvent = z.infer<typeof AgentSpeechEvent>;
|
|
36
36
|
|
|
37
|
-
export const
|
|
37
|
+
export const ErrorEvent = z.object({
|
|
38
38
|
event_type: z.literal("error"),
|
|
39
39
|
content: z.string(),
|
|
40
40
|
});
|
|
41
|
-
export type ErrorEvent = z.
|
|
41
|
+
export type ErrorEvent = z.infer<typeof ErrorEvent>;
|
|
42
42
|
|
|
43
|
-
export const
|
|
43
|
+
export const WarningEvent = z.object({
|
|
44
44
|
event_type: z.literal("warning"),
|
|
45
45
|
content: z.string(),
|
|
46
46
|
});
|
|
47
|
-
export type WarningEvent = z.
|
|
47
|
+
export type WarningEvent = z.infer<typeof WarningEvent>;
|
|
48
48
|
|
|
49
|
-
export const
|
|
49
|
+
export const AgentQuestionEvent = z.object({
|
|
50
50
|
event_type: z.literal("agent-question"),
|
|
51
51
|
question_id: z.string(),
|
|
52
52
|
question: z.string(),
|
|
53
53
|
});
|
|
54
|
-
export type AgentQuestionEvent = z.
|
|
54
|
+
export type AgentQuestionEvent = z.infer<typeof AgentQuestionEvent>;
|
|
55
55
|
|
|
56
|
-
export const
|
|
56
|
+
export const IntentEvent = z.object({
|
|
57
57
|
event_type: z.literal("intent"),
|
|
58
58
|
intent_id: z.string(),
|
|
59
59
|
intent_summary: z.string(),
|
|
60
60
|
});
|
|
61
|
-
export type IntentEvent = z.
|
|
61
|
+
export type IntentEvent = z.infer<typeof IntentEvent>;
|
|
62
62
|
|
|
63
|
-
export const
|
|
63
|
+
export const ActionItemCompletedEvent = z.object({
|
|
64
64
|
event_type: z.literal("action-item-done"),
|
|
65
65
|
key: z.string(),
|
|
66
66
|
payload: z.unknown(),
|
|
67
67
|
});
|
|
68
|
-
export type ActionItemCompletedEvent = z.
|
|
68
|
+
export type ActionItemCompletedEvent = z.infer<typeof ActionItemCompletedEvent>;
|
|
69
69
|
|
|
70
|
-
export const
|
|
70
|
+
export const TaskCompletedEvent = z.object({
|
|
71
71
|
event_type: z.literal("task-done"),
|
|
72
72
|
|
|
73
73
|
task_id: z.string(),
|
|
74
74
|
});
|
|
75
|
-
export type TaskCompletedEvent = z.
|
|
75
|
+
export type TaskCompletedEvent = z.infer<typeof TaskCompletedEvent>;
|
|
76
76
|
|
|
77
|
-
export const
|
|
77
|
+
export const OutboundCallConnected = z.object({
|
|
78
78
|
event_type: z.literal("outbound-call-connected"),
|
|
79
79
|
});
|
|
80
|
-
export type OutboundCallConnected = z.
|
|
80
|
+
export type OutboundCallConnected = z.infer<typeof OutboundCallConnected>;
|
|
81
81
|
|
|
82
|
-
export const
|
|
82
|
+
export const OutboundCallFailed = z.object({
|
|
83
83
|
event_type: z.literal("outbound-call-failed"),
|
|
84
84
|
|
|
85
85
|
error_code: z.int(),
|
|
86
86
|
error_reason: z.string(),
|
|
87
87
|
});
|
|
88
|
-
export type OutboundCallFailed = z.
|
|
88
|
+
export type OutboundCallFailed = z.infer<typeof OutboundCallFailed>;
|
|
89
89
|
|
|
90
|
-
export const
|
|
90
|
+
export const BotSessionEnded = z.object({
|
|
91
91
|
event_type: z.literal("bot-session-ended"),
|
|
92
92
|
});
|
|
93
|
-
export type BotSessionEnded = z.
|
|
94
|
-
|
|
95
|
-
export const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
93
|
+
export type BotSessionEnded = z.infer<typeof BotSessionEnded>;
|
|
94
|
+
|
|
95
|
+
export const ChoiceQueryEvent = z.object({
|
|
96
|
+
event_type: z.literal("choice-query"),
|
|
97
|
+
field_key: z.string(),
|
|
98
|
+
query: z.string(),
|
|
99
|
+
query_id: z.string(),
|
|
100
|
+
});
|
|
101
|
+
export type ChoiceQueryEvent = z.infer<typeof ChoiceQueryEvent>;
|
|
102
|
+
|
|
103
|
+
export const GuavaEvent = z.union([
|
|
104
|
+
SessionStartedEvent,
|
|
105
|
+
InboundCallEvent,
|
|
106
|
+
CallerSpeechEvent,
|
|
107
|
+
AgentSpeechEvent,
|
|
108
|
+
ErrorEvent,
|
|
109
|
+
WarningEvent,
|
|
110
|
+
AgentQuestionEvent,
|
|
111
|
+
IntentEvent,
|
|
112
|
+
ActionItemCompletedEvent,
|
|
113
|
+
TaskCompletedEvent,
|
|
114
|
+
OutboundCallConnected,
|
|
115
|
+
OutboundCallFailed,
|
|
116
|
+
BotSessionEnded,
|
|
117
|
+
ChoiceQueryEvent,
|
|
109
118
|
]);
|
|
110
|
-
export type GuavaEvent = z.
|
|
119
|
+
export type GuavaEvent = z.infer<typeof GuavaEvent>;
|
|
120
|
+
|
|
121
|
+
const _KNOWN_EVENT_TYPES = new Set(
|
|
122
|
+
GuavaEvent.options.map((schema) => schema.shape.event_type.value),
|
|
123
|
+
);
|
|
111
124
|
|
|
112
125
|
export function decodeEvent(
|
|
113
126
|
serialized_event: string | ArrayBuffer | Buffer | Buffer[],
|
|
114
127
|
): GuavaEvent | null {
|
|
115
128
|
let data: Record<string, any>;
|
|
116
|
-
if (typeof serialized_event
|
|
129
|
+
if (typeof serialized_event === "string") {
|
|
117
130
|
data = JSON.parse(serialized_event);
|
|
118
131
|
} else if (serialized_event instanceof ArrayBuffer) {
|
|
119
132
|
data = JSON.parse(new TextDecoder().decode(serialized_event));
|
|
120
|
-
} else if (serialized_event
|
|
133
|
+
} else if (Array.isArray(serialized_event)) {
|
|
121
134
|
let decoded = "";
|
|
122
135
|
for (const buf of serialized_event) {
|
|
123
136
|
decoded += buf.toString("utf8");
|
|
@@ -127,16 +140,18 @@ export function decodeEvent(
|
|
|
127
140
|
data = JSON.parse(serialized_event.toString("utf8"));
|
|
128
141
|
}
|
|
129
142
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
143
|
+
if (!_KNOWN_EVENT_TYPES.has(data.event_type)) {
|
|
144
|
+
process.emitWarning(
|
|
145
|
+
`Received an unknown event type ${data.event_type}. Update to a newer version of this SDK.`,
|
|
146
|
+
);
|
|
134
147
|
return null;
|
|
135
148
|
}
|
|
149
|
+
|
|
150
|
+
return GuavaEvent.parse(data);
|
|
136
151
|
}
|
|
137
152
|
|
|
138
|
-
export const
|
|
153
|
+
export const InboundTunnelEvent = z.object({
|
|
139
154
|
call_id: z.string(),
|
|
140
|
-
event:
|
|
155
|
+
event: GuavaEvent,
|
|
141
156
|
});
|
|
142
|
-
export type
|
|
157
|
+
export type InboundTunnelEvent = z.infer<typeof InboundTunnelEvent>;
|
package/src/example_data.ts
CHANGED
|
@@ -1,3 +1,45 @@
|
|
|
1
|
+
export function mockAppointmentsForFuture(nextNDays: number = 25): string[] {
|
|
2
|
+
const timeZone = "America/Los_Angeles";
|
|
3
|
+
const mockAppointments: string[] = [];
|
|
4
|
+
|
|
5
|
+
const now = new Date();
|
|
6
|
+
|
|
7
|
+
// Get today's date parts in America/Los_Angeles
|
|
8
|
+
const laDateParts = new Intl.DateTimeFormat("en-CA", {
|
|
9
|
+
timeZone,
|
|
10
|
+
year: "numeric",
|
|
11
|
+
month: "2-digit",
|
|
12
|
+
day: "2-digit",
|
|
13
|
+
}).formatToParts(now);
|
|
14
|
+
|
|
15
|
+
const year = Number(laDateParts.find((p) => p.type === "year")?.value);
|
|
16
|
+
const month = Number(laDateParts.find((p) => p.type === "month")?.value);
|
|
17
|
+
const day = Number(laDateParts.find((p) => p.type === "day")?.value);
|
|
18
|
+
|
|
19
|
+
const times = [
|
|
20
|
+
[9, 0],
|
|
21
|
+
[10, 30],
|
|
22
|
+
[13, 0],
|
|
23
|
+
[15, 30],
|
|
24
|
+
] as const;
|
|
25
|
+
|
|
26
|
+
for (let offset = 1; offset <= nextNDays; offset++) {
|
|
27
|
+
const baseDate = new Date(Date.UTC(year, month - 1, day + offset));
|
|
28
|
+
|
|
29
|
+
const y = baseDate.getUTCFullYear();
|
|
30
|
+
const m = String(baseDate.getUTCMonth() + 1).padStart(2, "0");
|
|
31
|
+
const d = String(baseDate.getUTCDate()).padStart(2, "0");
|
|
32
|
+
|
|
33
|
+
for (const [hour, minute] of times) {
|
|
34
|
+
const hh = String(hour).padStart(2, "0");
|
|
35
|
+
const mm = String(minute).padStart(2, "0");
|
|
36
|
+
mockAppointments.push(`${y}-${m}-${d}T${hh}:${mm}:00`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return mockAppointments;
|
|
41
|
+
}
|
|
42
|
+
|
|
1
43
|
export const PROPERTY_INSURANCE_POLICY = `HARPER VALLEY PROPERTY INSURANCE
|
|
2
44
|
COMPREHENSIVE RESIDENTIAL PROGRAM MANUAL
|
|
3
45
|
|
package/src/helpers/openai.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import OpenAI, { toFile } from "openai";
|
|
2
|
-
import {
|
|
2
|
+
import { zodTextFormat } from "openai/helpers/zod";
|
|
3
|
+
import { type Logger, getDefaultLogger } from "../logging.ts";
|
|
3
4
|
import * as z from "zod";
|
|
5
|
+
import { telemetryClient } from "../telemetry.ts";
|
|
4
6
|
|
|
5
7
|
// from beta.py
|
|
6
8
|
// TODO: Remove after beta
|
|
7
9
|
function beta_create_openai_client(logger: Logger) {
|
|
8
|
-
const baseUrl =
|
|
9
|
-
process.env.GUAVA_BASE_URL ?? "https://guava-dev.gridspace.com/";
|
|
10
|
+
const baseUrl = process.env.GUAVA_BASE_URL ?? "https://guava-dev.gridspace.com/";
|
|
10
11
|
// to get it working with OpenAI TS/JS client
|
|
11
12
|
const basedUrl = new URL("openai/v1/", baseUrl);
|
|
12
13
|
logger.info(`Creating beta OpenAI client`);
|
|
@@ -16,6 +17,7 @@ function beta_create_openai_client(logger: Logger) {
|
|
|
16
17
|
});
|
|
17
18
|
}
|
|
18
19
|
|
|
20
|
+
@telemetryClient.trackClass()
|
|
19
21
|
export class IntentRecognizer<Choices extends readonly string[]> {
|
|
20
22
|
private client: OpenAI;
|
|
21
23
|
private intentChoices: Choices;
|
|
@@ -43,40 +45,36 @@ Possible Choices: ${this.intentChoices}.
|
|
|
43
45
|
}
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
@telemetryClient.trackClass()
|
|
46
49
|
export class DocumentQA {
|
|
47
50
|
private client: OpenAI;
|
|
48
51
|
private vector_store: Promise<OpenAI.VectorStore>;
|
|
49
52
|
private logger: Logger;
|
|
53
|
+
|
|
50
54
|
constructor(
|
|
51
55
|
vector_store_name: string,
|
|
52
56
|
document: string,
|
|
53
|
-
logger: Logger,
|
|
57
|
+
logger: Logger = getDefaultLogger(),
|
|
54
58
|
client?: OpenAI,
|
|
55
59
|
) {
|
|
56
60
|
this.client = client ?? beta_create_openai_client(logger);
|
|
57
|
-
this.vector_store = this.getOrCreateVectorStore(
|
|
58
|
-
vector_store_name,
|
|
59
|
-
document,
|
|
60
|
-
);
|
|
61
|
+
this.vector_store = this.getOrCreateVectorStore(vector_store_name, document);
|
|
61
62
|
this.logger = logger;
|
|
62
63
|
}
|
|
63
64
|
|
|
64
65
|
async getOrCreateVectorStore(vector_store_name: string, document: string) {
|
|
65
66
|
const encoder = new TextEncoder();
|
|
66
67
|
const document_buffer = encoder.encode(document);
|
|
67
|
-
const document_hash_buffer = await crypto.subtle.digest(
|
|
68
|
-
"SHA-256",
|
|
69
|
-
document_buffer,
|
|
70
|
-
);
|
|
68
|
+
const document_hash_buffer = await crypto.subtle.digest("SHA-256", document_buffer);
|
|
71
69
|
const u8view = new Uint8Array(document_hash_buffer);
|
|
72
70
|
const document_hash: string = Array.from(u8view)
|
|
73
71
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
74
72
|
.join("");
|
|
75
73
|
for await (const vs of this.client.vectorStores.list()) {
|
|
76
74
|
if (
|
|
77
|
-
vs.name
|
|
75
|
+
vs.name === vector_store_name &&
|
|
78
76
|
vs.metadata &&
|
|
79
|
-
vs.metadata
|
|
77
|
+
vs.metadata.document_hash === document_hash
|
|
80
78
|
) {
|
|
81
79
|
this.logger.info("Re-using existing vector store...");
|
|
82
80
|
return vs;
|
|
@@ -96,10 +94,7 @@ export class DocumentQA {
|
|
|
96
94
|
this.logger.info("Uploading file...");
|
|
97
95
|
await this.client.vectorStores.files.uploadAndPoll(
|
|
98
96
|
vector_store.id,
|
|
99
|
-
await toFile(
|
|
100
|
-
new Blob([document], { type: "text/plain" }),
|
|
101
|
-
"document.txt",
|
|
102
|
-
),
|
|
97
|
+
await toFile(new Blob([document], { type: "text/plain" }), "document.txt"),
|
|
103
98
|
);
|
|
104
99
|
|
|
105
100
|
this.logger.info("Updating vector store metadata...");
|
|
@@ -131,3 +126,63 @@ export class DocumentQA {
|
|
|
131
126
|
return response.output_text;
|
|
132
127
|
}
|
|
133
128
|
}
|
|
129
|
+
|
|
130
|
+
const filterSchema = z.object({
|
|
131
|
+
matchingAppointments: z.array(z.string()).describe("List of datetimes matching the query."),
|
|
132
|
+
otherAppointments: z
|
|
133
|
+
.array(z.string())
|
|
134
|
+
.describe("If no datetimes match the query, list of datetimes to suggest."),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
@telemetryClient.trackClass()
|
|
138
|
+
export class DatetimeFilter {
|
|
139
|
+
private client: OpenAI;
|
|
140
|
+
private sourceList: string[];
|
|
141
|
+
|
|
142
|
+
constructor(
|
|
143
|
+
{ sourceList, client }: { sourceList: string[]; client?: OpenAI },
|
|
144
|
+
logger: Logger = getDefaultLogger(),
|
|
145
|
+
) {
|
|
146
|
+
this.sourceList = sourceList;
|
|
147
|
+
this.client = client ?? beta_create_openai_client(logger);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async filter(
|
|
151
|
+
query: string,
|
|
152
|
+
{ maxResults = 5 }: { maxResults?: number } = {},
|
|
153
|
+
): Promise<[string[], string[]]> {
|
|
154
|
+
const appointmentTimesStr = this.sourceList.join("\n");
|
|
155
|
+
const today = new Date().toLocaleDateString("en-US", {
|
|
156
|
+
month: "long",
|
|
157
|
+
day: "numeric",
|
|
158
|
+
year: "numeric",
|
|
159
|
+
});
|
|
160
|
+
const prompt = `\
|
|
161
|
+
Return a few datetimes in the list matching the query. If no datetimes match the query, return a few other datetimes from the list that can be used as close suggestions.
|
|
162
|
+
NEVER HALLUCINATE DATETIMES THAT ARE NOT IN THE LIST.
|
|
163
|
+
Query: ${query}
|
|
164
|
+
Today's Date: ${today}
|
|
165
|
+
Appointment Times:
|
|
166
|
+
${appointmentTimesStr}
|
|
167
|
+
==================
|
|
168
|
+
You must return at most ${maxResults} options per list.`;
|
|
169
|
+
|
|
170
|
+
const response = await this.client.responses.parse({
|
|
171
|
+
model: "gpt-5-mini",
|
|
172
|
+
input: [{ role: "system", content: prompt }],
|
|
173
|
+
text: { format: zodTextFormat(filterSchema, "filter") },
|
|
174
|
+
reasoning: { effort: "medium" },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const output = response.output_parsed;
|
|
178
|
+
|
|
179
|
+
if (!output) {
|
|
180
|
+
throw new Error("Failed to produce parseable output.");
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return [
|
|
184
|
+
output.matchingAppointments.slice(0, maxResults),
|
|
185
|
+
output.otherAppointments.slice(0, maxResults),
|
|
186
|
+
];
|
|
187
|
+
}
|
|
188
|
+
}
|