Function Instruction Type | Yext Hitchhikers Platform

Function steps leverage the Yext Functions framework to give Yext Chat access to user-defined serverless Typescript functions. This allows the bot to perform complex tasks like:

  • Transforming, reading, and writing data to external APIs
  • Using open source libraries to manipulate or analyze data
  • Performing complex business logic

The function has access to the full history of the conversation, including any collected data, as well as any additional context data you’ve provided.

Additionally, the return value of the function is saved into the queryResult, so the bot can use it to answer questions.

Let’s walk through an example.

Example

In this example, we’ll show a function that uses OpenWeatherMap to fetch the weather in a specific city.

To use a function in an instruction, simply specify the pluginId (a plugin is a collection of functions) and the functionName.

{
  "$id": "example",
  "$schema": "https://schema.yext.com/config/chat/chat-bot/v1",
  "name": "Example",
  "initialMessage": "Hello! How are you doing?",
  "goals": {
    "GET-WEATHER": {
      "goal": "Get the weather in a city.",
      "examples": [
        "How's the weather in San Francisco?",
        "What's the weather in Los Angeles?",
        "What's the weather in San Diego?"
      ],
      "instructions": [
        {
          "collect": {
            "instruction": "Ask the user what city they're interested in",
            "fields": [
              {
                "fieldType": "STRING",
                "id": "city",
                "optional": false
              }
            ]
          }
        },
        {
          "function": {
            "functionName": "getWeather",
            "pluginId": "weatherPlugin"
          }
        },
        {
          "reply": {
            "instruction": "Based on the response, tell them the weather in that city. You must ALWAYS respond in Farenheit. You may convert the temperature from Kelvin/Celsius to Farenheit if needed.",
            "mode": "DIRECT_ANSWER"
          }
        }
      ]
    }
  }
}

So what should the function actually look like? Yext functions receive a payload object with the following keys:

  • messages - the message history
  • notes - the “notes” stored in the conversation, which includes the queryResult from previous messages, the predictedGoal, and more
  • context - any additional metadata passed to the Chat API, which might include details about the user or other contextual information that might influence the chat bot

(In other words, the Yext function has access to the exact same data that the Chat API has access to.)

Therefore a function to fetch data based on the “city” collected from the user might look like this:

export const getWeather = async ({ messages, notes }) => {
  const city = notes.collectedData.city;
  if (!city) {
    throw new Error("No city provided".)
  }
  const params = {
    city,
    apiKey: "<YOUR_API_KEY_HERE>",
  };
  const url = `https://api.openweathermap.org/data/2.5/weather?q=${params.city}&appid=${params.apiKey}`;
  const response = await fetch(url);
  const data = await response.json();
  return {
    queryResult: data,
  };
};

In this function, we first confirm that a city was collected (which should always be the case, because the function step is preceded by a collect step that prompts the user for a city.

Next, we send an API request to an API and put its response in the queryResult portion of the response, which makes it available to the next step in the instructions.

Typing the Function

Yext functions use the Deno runtime, which supports Typescript out of the box. If you want Typescript typings for your Chat serverless functions, you can use the types below, which are written using Zod, so that you can use them for runtime validation as well.

import { z } from "https://deno.land/x/zod/mod.ts";

const ChatFunctionPayloadSchema = z.object({
  messages: z.array(
    z.object({
      text: z.string(),
      timestamp: z.string(),
      source: z.enum(["USER", "BOT"]),
    })
  ),
  notes: z
    .object({
      currentGoal: z.string().optional(),
      currentStepIndices: z.array(z.number()).optional(),
      searchQuery: z.string().optional(),
      queryResult: z.any(),
      collectedData: z.record(z.string()).optional(),
      goalFirstMsgIndex: z.number().optional(),
    })
    .optional(),
});

export type ChatFunctionPayload = z.infer<typeof ChatFunctionPayloadSchema>;

export type ChatFunctionReturn = Partial<ChatFunctionPayload["notes"]>;

export type ChatFunction = (
  payload: ChatFunctionPayload
) => Promise<ChatFunctionReturn>;