feat: support auto create agent (#23493)
* feat: support auto create agent * feat: add prompts * chore: add number to call * fix typo --------- Co-authored-by: Amit Sharma <74371312+Amit91848@users.noreply.github.com> Co-authored-by: CarinaWolli <wollencarina@gmail.com>
This commit is contained in:
co-authored by
Amit Sharma
CarinaWolli
parent
4ca78f46df
commit
ba50268634
@@ -35,7 +35,7 @@ export const DEFAULT_PROMPT_VALUE = `## You are helping user set up a call with
|
||||
Avoid multiple questions in a single response.
|
||||
Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.
|
||||
Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).
|
||||
If you are saying a time like 8:00 AM, just say 8 AM and emit the trailing zeros.
|
||||
If you are saying a time like 8:00 AM, just say 8 AM and omit the trailing zeros.
|
||||
|
||||
## Response Guideline
|
||||
Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.
|
||||
@@ -53,7 +53,7 @@ export const DEFAULT_PROMPT_VALUE = `## You are helping user set up a call with
|
||||
- if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
|
||||
- if availability does not exist, ask user to select another time range for the appointment, repeat this step 3.
|
||||
5. Confirm the date and time selected by user: \"Just to confirm, you want to book the appointment at ...\".
|
||||
6. Once confirmed, call function book_appointment_{{eventTypeId}} to book the appointment.
|
||||
6. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
|
||||
- if booking returned booking detail, it means booking is successful, proceed to step 7.
|
||||
- if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3.
|
||||
7. Inform the user booking is successful, and ask if user have any questions. Answer them if there are any.
|
||||
|
||||
@@ -155,6 +155,7 @@ export class CallService {
|
||||
EVENT_START_TIME_IN_ATTENDEE_TIMEZONE: "2:00 PM",
|
||||
EVENT_END_TIME_IN_ATTENDEE_TIMEZONE: "2:30 PM",
|
||||
eventTypeId: eventTypeId.toString(),
|
||||
NUMBER_TO_CALL: toNumber,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
const styleGuardrails = `## Style Guardrails
|
||||
Be Concise: Respond succinctly, addressing one topic at most.
|
||||
Embrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content.
|
||||
Be Conversational: Use everyday language, making the chat feel like talking to a friend.
|
||||
Be Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion.
|
||||
Avoid multiple questions in a single response.
|
||||
Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.
|
||||
Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).
|
||||
If you are saying a time like 8:00 AM, just say 8 AM and omit the trailing zeros.`;
|
||||
|
||||
const responseGuideline = `## Response Guideline
|
||||
Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.
|
||||
Stay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating.
|
||||
Ensure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.`;
|
||||
|
||||
const scheduleRule = ` ## Schedule Rule
|
||||
Current time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.`;
|
||||
|
||||
// Key are from components/sections/template/data/workflows.ts page in https://github.com/calcom/website
|
||||
export const calAIPhoneWorkflowTemplates = {
|
||||
// name: "Cal AI No-show Follow-up Call",
|
||||
// description: "Automatically call attendee when marked as no-show"
|
||||
"wf-10": {
|
||||
generalPrompt: `## You are calling an attendee who was marked as a no-show for their appointment. Your goal is to help them reschedule. Be understanding, friendly, and non-judgmental.
|
||||
|
||||
${styleGuardrails}
|
||||
|
||||
${responseGuideline}
|
||||
|
||||
${scheduleRule}
|
||||
|
||||
## Task Steps
|
||||
1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a courtesy call from {{ORGANIZER_NAME}}. I noticed you weren't able to make your {{EVENT_NAME}} appointment on {{EVENT_DATE}} at {{EVENT_TIME}}."
|
||||
2. Express understanding: "I understand things come up. I'm calling to see if you'd like to reschedule for another time that works better for you."
|
||||
3. If they want to reschedule:
|
||||
3a. Ask "When would work best for you to reschedule?"
|
||||
3b. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range.
|
||||
- if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
|
||||
- if availability does not exist, ask user to select another time range for the appointment, repeat this step 3a.
|
||||
5. If {{ATTENDEE_EMAIL}} is not unknown then Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking else Ask for user name and email and Confirm the name and email with user by reading it back to user.
|
||||
6. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
|
||||
- if booking returned booking detail, it means booking is successful, proceed to step 7.
|
||||
- if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3a.
|
||||
7. If they don't want to reschedule:
|
||||
- Thank them for their time and let them know they can always reach out if they change their mind.
|
||||
8. Before ending, ask if there's anything else you can help with.
|
||||
9. Thank them for their time and call function end_call to hang up.`,
|
||||
},
|
||||
|
||||
// name: "Cal AI 1-hour Meeting Reminder",
|
||||
// description: "Remind attendee 1 hour before the meeting"
|
||||
"wf-11": {
|
||||
generalPrompt: `## You are calling to remind an attendee about their upcoming appointment in 1 hour. Be friendly, helpful, and concise.
|
||||
|
||||
${styleGuardrails}
|
||||
|
||||
${responseGuideline}
|
||||
|
||||
${scheduleRule}
|
||||
|
||||
## Task Steps
|
||||
1. Start with a friendly greeting: "Hi {{ATTENDEE_NAME}}, this is a quick reminder call from {{ORGANIZER_NAME}} about your upcoming {{EVENT_NAME}} appointment."
|
||||
2. Provide the meeting details: "Your appointment is scheduled for today at {{EVENT_TIME}} {{TIMEZONE}}. That's in about an hour."
|
||||
3. Ask if they'll be able to make it: "Will you be able to join us?"
|
||||
4. If they confirm attendance:
|
||||
- Thank them and remind them of any preparation needed.
|
||||
- Say "Great! We'll see you at {{EVENT_TIME}}."
|
||||
5. If they need to reschedule or cancel:
|
||||
- Express understanding: "No problem, these things happen."
|
||||
- Ask: "Would you like to reschedule now, or would you prefer to contact us later?"
|
||||
- If they want to reschedule now:
|
||||
5a. If {{ATTENDEE_EMAIL}} is not unknown: Use name {{ATTENDEE_NAME}} and email {{ATTENDEE_EMAIL}} for creating booking
|
||||
5b. If {{ATTENDEE_EMAIL}} is unknown: Ask for user name and email and confirm by reading it back to user
|
||||
5c. Ask user for "When would you want to reschedule?"
|
||||
5d. Call function check_availability_{{eventTypeId}} to check for availability in the user provided time range.
|
||||
5e. If availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
|
||||
5f. If availability does not exist, ask user to select another time range for the appointment (repeat step 5c).
|
||||
5g. Confirm the date and time selected by user: "Just to confirm, you want to book the appointment at ..."
|
||||
5h. Once confirmed, you can use {{NUMBER_TO_CALL}} as phone number for creating booking and call function book_appointment_{{eventTypeId}} to book the appointment.
|
||||
5i. If booking returned booking detail, it means booking is successful, proceed to step 7.
|
||||
5j. If booking returned error message, let user know why the booking was not successful, and maybe start over (return to step 5c).
|
||||
- If they prefer to reschedule later: "No problem. You can reschedule anytime through the link in your confirmation email or by contacting us."
|
||||
6. If they have questions about the meeting:
|
||||
- Answer based on available information ({{ADDITIONAL_NOTES}}, {{LOCATION}}, etc.).
|
||||
- Common questions to handle:
|
||||
- Duration: Use {{EVENT_END_TIME}} to calculate and state duration
|
||||
- Location details: Provide {{LOCATION}} information
|
||||
- What to prepare: Check {{ADDITIONAL_NOTES}} for any preparation instructions
|
||||
- Who they're meeting: {{ORGANIZER_NAME}} is the person they'll be meeting
|
||||
7. End with: "Thanks for your time. Have a great day!" and call function end_call to hang up.`,
|
||||
},
|
||||
};
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { WorkflowStep } from "@prisma/client";
|
||||
import { type TFunction } from "i18next";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useParams, useRouter, useSearchParams } from "next/navigation";
|
||||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
@@ -128,8 +128,10 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
const { t, i18n } = useLocale();
|
||||
const utils = trpc.useUtils();
|
||||
const params = useParams();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const { step, form, reload, setReload, teamId } = props;
|
||||
const { step, form, reload, setReload, teamId, onSaveWorkflow } = props;
|
||||
const { data: _verifiedNumbers } = trpc.viewer.workflows.getVerifiedNumbers.useQuery(
|
||||
{ teamId },
|
||||
{ enabled: !!teamId }
|
||||
@@ -158,7 +160,6 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
onSuccess: async (data) => {
|
||||
showToast(t("agent_created_successfully"), "success");
|
||||
|
||||
// Update the step's agentId in the form state
|
||||
if (step) {
|
||||
const stepIndex = step.stepNumber - 1;
|
||||
form.setValue(`steps.${stepIndex}.agentId`, data.id);
|
||||
@@ -239,6 +240,66 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
);
|
||||
|
||||
const [timeSectionText, setTimeSectionText] = useState(getTimeSectionText(form.getValues("trigger"), t));
|
||||
const [autoAgentCreationAttempted, setAutoAgentCreationAttempted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const autoCreateAgent = searchParams?.get("autoCreateAgent");
|
||||
const templateWorkflowId = searchParams?.get("templateWorkflowId");
|
||||
|
||||
if (
|
||||
autoCreateAgent === "true" &&
|
||||
!autoAgentCreationAttempted &&
|
||||
templateWorkflowId &&
|
||||
step &&
|
||||
step.action === WorkflowActions.CAL_AI_PHONE_CALL &&
|
||||
!stepAgentId &&
|
||||
step.id &&
|
||||
onSaveWorkflow
|
||||
) {
|
||||
setAutoAgentCreationAttempted(true);
|
||||
|
||||
const createAgent = async () => {
|
||||
try {
|
||||
await onSaveWorkflow?.();
|
||||
|
||||
const updatedSteps = form.getValues("steps");
|
||||
const currentStepIndex = step.stepNumber - 1;
|
||||
const updatedStep = updatedSteps[currentStepIndex];
|
||||
|
||||
if (updatedStep?.id) {
|
||||
createAgentMutation.mutate({
|
||||
teamId,
|
||||
workflowStepId: updatedStep.id,
|
||||
templateWorkflowId,
|
||||
});
|
||||
|
||||
const url = new URL(window.location.href);
|
||||
url.searchParams.delete("autoCreateAgent");
|
||||
url.searchParams.delete("templateWorkflowId");
|
||||
router.replace(url.pathname + url.search);
|
||||
} else {
|
||||
showToast(t("failed_to_get_workflow_step_id"), "error");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to auto-create agent:", error);
|
||||
showToast(t("failed_to_create_agent"), "error");
|
||||
}
|
||||
};
|
||||
|
||||
createAgent();
|
||||
}
|
||||
}, [
|
||||
searchParams,
|
||||
autoAgentCreationAttempted,
|
||||
step,
|
||||
stepAgentId,
|
||||
teamId,
|
||||
onSaveWorkflow,
|
||||
createAgentMutation,
|
||||
form,
|
||||
t,
|
||||
router,
|
||||
]);
|
||||
|
||||
const { data: actionOptions } = trpc.viewer.workflows.getWorkflowActionOptions.useQuery();
|
||||
const triggerOptions = getWorkflowTriggerOptions(t);
|
||||
@@ -681,8 +742,8 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) {
|
||||
color="secondary"
|
||||
onClick={async () => {
|
||||
// save the workflow first to get the step id
|
||||
if (props.onSaveWorkflow) {
|
||||
await props.onSaveWorkflow();
|
||||
if (onSaveWorkflow) {
|
||||
await onSaveWorkflow();
|
||||
|
||||
// After saving, get the updated step ID from the form
|
||||
const updatedSteps = form.getValues("steps");
|
||||
|
||||
@@ -183,6 +183,7 @@ export async function executeAIPhoneCall(payload: string) {
|
||||
ATTENDEE_FIRST_NAME: attendeeFirstName,
|
||||
ATTENDEE_LAST_NAME: attendeeLastName,
|
||||
ATTENDEE_EMAIL: attendee?.email || "",
|
||||
NUMBER_TO_CALL: numberToCall,
|
||||
ATTENDEE_TIMEZONE: attendee?.timeZone || "",
|
||||
ADDITIONAL_NOTES: booking.description || "",
|
||||
EVENT_START_TIME_IN_ATTENDEE_TIMEZONE: dayjs(booking.startTime)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createDefaultAIPhoneServiceProvider } from "@calcom/features/calAIPhone";
|
||||
import type { RetellLLMGeneralTools } from "@calcom/features/calAIPhone/providers/retellAI/types";
|
||||
import { calAIPhoneWorkflowTemplates } from "@calcom/features/calAIPhone/workflowTemplates";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import type { TCreateInputSchema } from "./create.schema";
|
||||
@@ -12,16 +13,21 @@ type CreateHandlerOptions = {
|
||||
};
|
||||
|
||||
export const createHandler = async ({ ctx, input }: CreateHandlerOptions) => {
|
||||
const { teamId, name, workflowStepId, ...retellConfig } = input;
|
||||
const { teamId, name, workflowStepId, templateWorkflowId, ...retellConfig } = input;
|
||||
|
||||
const aiService = createDefaultAIPhoneServiceProvider();
|
||||
|
||||
const generalPrompt = templateWorkflowId
|
||||
? calAIPhoneWorkflowTemplates?.[templateWorkflowId as keyof typeof calAIPhoneWorkflowTemplates]
|
||||
?.generalPrompt
|
||||
: undefined;
|
||||
|
||||
return await aiService.createAgent({
|
||||
name,
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
workflowStepId,
|
||||
generalPrompt: retellConfig.generalPrompt,
|
||||
generalPrompt: generalPrompt ?? retellConfig.generalPrompt,
|
||||
beginMessage: retellConfig.beginMessage,
|
||||
generalTools: retellConfig.generalTools as RetellLLMGeneralTools,
|
||||
userTimeZone: ctx.user.timeZone,
|
||||
|
||||
@@ -4,6 +4,7 @@ export const ZCreateInputSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
teamId: z.number().optional(),
|
||||
workflowStepId: z.number().optional(),
|
||||
templateWorkflowId: z.string().optional(),
|
||||
generalPrompt: z.string().optional(),
|
||||
beginMessage: z.string().optional(),
|
||||
generalTools: z
|
||||
|
||||
Reference in New Issue
Block a user