Beta

Please contact a Glean representative if you are interested in partnering with Glean and building tools on Glean’s platform.

Example tools

In this document, we will explore various examples that demonstrate how to enhance the capabilities of Glean Assistant by incorporating knowledge not already indexed in Glean's systems. Additionally, we'll show you how to enable Glean Assistant to perform specific "Actions," such as creating Jira issues or Salesforce cases. Before diving into this guide, please make sure you have gone through the Introduction and Getting Started pages to gain an understanding of the Glean tools framework.

Example of an execution action that allows Glean Assistant to update Google Documents

In this example, we will create a Google Doc Updater action from scratch. This action can help perform any operation on a pre-existing document. This example helps to demonstrate a full walkthrough of how actions are set up. This will also act as a guide for using OpenAPI specs with nested parameters.

Step 1: Finding the right OpenAPI Spec

The first step is to find the right OpenAPI Spec. For this, we will call the following HTTP request:

POST https://docs.googleapis.com/v1/documents/{documentId}:batchUpdate

  • OpenAPI specs for Google APIs usually aren't made available by Google. You can rely on the documentation made publicly available by Google here and construct a spec yourself with some help from Glean Assistant. If you want the entire raw OpenAPI spec, you can use 3rd party sources such as this (which contain API specs for all the paths in Google Docs API).
  • Glean Assistant uses the OpenAPI specification to understand the requirements of the server. We can thus form a well-defined valid request. When the user creates an action with a particular API spec, the action determines the URL and path to hit from these API spec fields. To make a valid request, we need to properly populate the request body, path parameter, and query parameter fields. The LLM is responsible for filling these fields correctly taking into account the properties like: type, enums, descriptions, and required. Some other fields like format, min, max, default, example, are also helpful to determine the field value. When the user makes a query that triggers this action, LLM fills the relevant fields and the response is displayed to the user giving him a chance to validate and edit the LLM filled fields before making the actual request.
  • Whatever source you choose, we must ensure that the final OpenAPI spec you use conforms to the abovementioned rules. To do that, we will:
    • Remove all the unnecessary paths and HTTP methods. There should only be a /v1/documents/{documentId}:batchUpdate path with a POST HTTP request.
    • You can delete any unnecessary fields from the request body. In this example, we removed a lot of operations and decided to keep only the insertText and replaceAllText operations.
    • You can simplify the schema of the response body with a string for write actions. In most cases, we do not consume the response body fields. Just note that fields like resultUrl from the responseBody may be used to plumb the correct Url to the end user for execute write actions. Example of simplification:
      Copy
      Copied
      responses:
       '200':
         description: OK
         content:
           application/json:
             schema:
               type: string

      Or even something as simple as:

      Copy
      Copied
      responses:
       '200':
         description: Successful Response
    • For ease while editing, resolve all the references (if any). We do support refs but editing while keeping refs is a bit trickier. Here is a list of all the OpenAPI spec features we currently support. You can use any online/dev tools for this. Some helpful tools: [NPM] , [Golang] , [Java] .
    • For this particular example I resolved all the $refs first, then deleted some unnecessary keys (including the ones for sectionId) and updated the field description and titles in a few cases. You can even define enums, maxItems (for arrays only) and required fields.
    • Filling the descriptions is an important step to guide the Action on how we want it to populate certain fields. You can see that I removed some unnecessary descriptions (from the 3rd Party API spec) and added some description of my own (for eg. the description for documentId in path parameters). Without the proper description for documentId, the Action wouldn't be able to locate which google document to update hence failing the request.
    • In the case of enum type values with not-so-descriptive values, you should describe the meaning of those values in the description field of the key.
    • Filling the Title for simple fields within the OpenAPI spec is optional but strongly recommended to provide the end-users a human readable way of understand the meaning of each field to be filled. In the given example, I've filled one for the matchCase key.
    • Incase of single-dimensional arrays, the simple fields are appended with a 1-based array index after the human-readable title. However, they're not supported for arrays with more than one dimensions, hence we fallback. For cases where the title is the same for two distinct keys within the OpenAPI spec, we fallback for all the keys. For any key fallback, we generate a prettified version of the key name. For example a key of type: request[2].address.city gets converted into Request <3> → Address → City .
    • Glean's internal typehints can be used in certain scenarios for example emails or text-content. In the API spec below, we could've added a x-glean-typehint: Content for the text field under the insertText operation. This would've saved us writing a detailed description for the text field, and just the first line: The text to be inserted. would've been enough.
    • For any arrays, you can define a maxItems field. The max possible number of elements will be bound by this and every sub-element within the item schema also contributes to the total number of valid keys counted by a factor of maxItems.
    • We have a hard limit of 15 keys max for any action. For example, below we have 5 simple keys (2 from insertText and 3 from replaceAllText). Since these properties come under the requests schema, they're multiplied by 2 i.e. our final keys are 10 from requestBody and 1 key from the parameter. These 11 keys are well under 15 keys, thus we support this OpenAPI specification in our action.
  • By the end, your OpenAPI spec may look something like this (note the description and title fields we added/edited/removed):
Copy
Copied
openapi: 3.0.0
servers:
  - url: 'https://docs.googleapis.com/'
info:
  description: Writes Google Docs documents.
  title: Google Docs API
  version: v1
paths:
  '/v1/documents/{documentId}:batchUpdate':
    post:
      description: >-
        Applies one or more updates to the document. Each request is validated
        before being applied. If any request is not valid, then the entire
        request will fail and nothing will be applied. The updates in your request
        are guaranteed to be applied together atomically.
      operationId: docs.documents.batchUpdate
      parameters:
        - description: >-
            The ID of the document to update. Use glean search to find 
            the relevant document. The documentId usually exists in the
            url of the doc. The syntax of URL (with documentId) is like:
            https://docs.google.com/document/d/<documentId>/edit
            Extract out the documentId from the URL.
          in: path
          name: documentId # 1 key
          required: true
          schema:
            type: string
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                requests: # 10 keys
                  type: array
                  maxItems: 2
                  items: # 5 keys
                    type: object
                    properties:
                      insertText: # 2 keys
                        description: >-
                          Inserts text at the specified location. Always use this 
                          operation except when text deletion or text replacement 
                          is required. 
                        type: object
                        properties:
                          location:
                            type: object
                            properties:
                              index:
                                description: >-
                                  The zero-based index, in UTF-16 code units. 
                                  Strictly use a value of 1 and nothing else.
                                format: int32
                                type: integer
                          text:
                            description: >-
                              The text to be inserted. This is the most important field
                              for this text inserting operation. Insert a neatly 
                              formatted text consisting of multiple paragraphs. Whenever
                              required, always start with a new line for paragraphs, 
                              tables, lists for neatness. Use bullet icons (or 
                              numbers for a numbered list) for any list. If user 
                              requires a table, use ASCII characters for generating a 
                              SQL style table. Use plaintext to form neat and formatted
                              text as much as possible.
                            type: string
                      replaceAllText: # 3 keys
                        description: >- 
                          Replaces all instances of the specified text. 
                          Always use this operation when text deletion 
                          or text replacement is required. 
                        type: object
                        properties:
                          containsText: # 2 keys
                            type: object
                            properties:
                              matchCase:
                                description: >-
                                  Indicates whether the search should respect
                                  case: - `True`: the search is case sensitive.
                                  - `False`: the search is case insensitive.
                                type: boolean
                                title: Do you want to match case
                              text:
                                description: The text to search for in the document.
                                type: string
                          replaceText:
                            description: >- 
                              The text that will replace the
                              matched text. Keep empty for deletion.
                            type: string
      responses:
        '200':
          description: Successful response
  • Feel free to remove any verbose fields (tags, info, x-(internal) fields, external docs, security) from this API Spec. Your API Spec is ready for use. We're mainly concerned with only a few sections under the servers (url) and paths fields (parameters, requestBody).

Step 2: Creating the action from scratch

  • Go to Workspace > Platform > Actions , and click on the "New Action" button.
  • Select start from scratch, since the Google Docs action isn't available in the templates.
  • Basic Information: Fill this section with information like display name, description, and unique identifier. We will use the unique identifier throughout the oAuth, action-testing, and setup phase. Ensure that the action type is Action because this action will update a Google doc on our behalf.

Basic Info Dialog Box

  • Trigger Condition: Please provide a detailed description of the action along with detailed instructions on when (in what cases) this action must be invoked/used by the Glean assistant. Also specify any information on when this action should not be invoked.

Trigger Condition Dialog Box

  • Functionality : Fill in the modified OpenAPI spec we made in step 1 of this tutorial. We are not using a server hosted in our private network (on-prem server) for this and are directly hitting Google API servers. Thus, we don't need to tick the "Use existing on-premise server to proxy requests from this Action" section.

Functionality Dialog Box

  • After this, please authorize the action with OAuth2.0 verification. Choose oAuth Admin. For further steps, we first need to authorize the Glean action in the Google APIs dashboard.

Step 3: Authorizing the action

First, authorize this action with proper permissions and credentials. Here, let's follow the example of how to configure the Authorization for Google APIs.

GCP Credentials Dialog Box

  • Select "Web Application" as the Application Type and give a name to your oAuth client.

Create oAuth Client

  • Assign "Authorized redirect URIs" (not the "Authorized JavaScript origins" section) to validate the actions's auth from Glean's backend. Add this URI:
Copy
Copied
https://{{your-glean-domain-name}}-be.glean.com/tools/oauth/verify_code/{{your-action-unique-identifier-name}}

For example:
https://{{your-glean-domain-name}}-be.glean.com/tools/oauth/verify_code/updateGoogleDoc

Ensure that you fill in the unique identifier name of the action and not the display name of the action. Please note that the unique identifier for your action is case sensitive. To get your glean domain name, follow the steps here.

  • Click the save button and you will get access to the client ID and client secret. If not, you can create a new secret too. Note that you can also use another pre-existing client ID too. Just add the new URI to the existing URIs list.
  • Go back to the glean's action creation page and in the Authentication window, fill in the client ID and client secret we got from the oAuth client.
  • Fill in the following client URL, auth URL, and scopes:
Copy
Copied
Client URL: https://accounts.google.com/o/oauth2/auth?prompt=consent&access_type=offline

Authorization URL: https://accounts.google.com/o/oauth2/token

Scopes: https://www.googleapis.com/auth/documents

These details are usually present in the oAuth section of the API documentation for that action. Save the action. The final view should be something like this:

Authentication Dialogue Box

  • Turn on Google Docs API for your GCP project from here:
Copy
Copied
https://console.cloud.google.com/apis/api/docs.googleapis.com/metrics?project={{your-gcp-project-name}}

Enable the Docs API. It should look something like this after turning it on:

GCP Google Docs API Settings

  • Now that we've done all this, return to the Glean's action creation page again. If you haven't saved the action yet, click save. Now, click on the "Authorize Action" button. It should redirect you to Google's Authorization page. You may have to sign in. Once done and successful, you will be redirected to the original action creation page.

Authorize Action Link

  • You should see the green tick establishing that you've authorized your action.

Step 4: Deploy and Use the action

Now that we've successfully created the action, it's time to test it out.

  • To briefly test the action, you can click the link shown below "Test your Action at this URL". Note that this can only be tested by the action creator.
  • Issue a query on this new URL, you should see the query executed and a box should appear to "Review and confirm" the action.

Gleanchat Action Trigger View

  • Let's check the state of the document before issuing the query.

State of Doc before Action Execution

  • Click on "Review and confirm". A window will pop up. Check the documentId and other fields of the document and make necessary changes if required.
  • The field names shown here are taken from the title of each field in the API Spec. To ensure that more user friendly names are shown here, please add title to each field in the API Spec.

Chat Execution Modal Form

  • Once this is done, click on the Save Changes button. Once the pinwheel appears and disappears, you should either see an error message or the same screen back.
  • If there is an error, maybe it's an authentication issue. Try to resolve this on your end and if required, reach out to us.
  • If it returns to the same screen, cross the box and visit that same document. You would likely see the document in the Reading section. Let's see the doc after the call is made.

State of Doc after Action Execution

  • Great! This call was successful and has replaced the text while ignoring the case.
  • Once you've tested this out, you can deploy this action to Chat or AI Apps. Head to the deploy tab, right next to the setup tab on the action creation page.

Deploy Action View

  • As an admin, you can choose to enable this for all teammates or some teammates. These settings can only be modified by the admin. Once done with this, click on the save button and you should be able to run the action in normal chat flow if it's enabled for you.
Example of a retrieval tool that allows Glean Assistant to fetch Google calendar events

First, you would need to create a tool manifest that describes basic properties of a tool. See Defining a tool manifest to learn more about creating a manifest for different types of tools. For a retrieval tool with just service level authentication, the manifest would look like this -:

Copy
Copied
{
  "type": "RETRIEVAL",
  "name": "RetrieveGoogleCalendarEvents",
  "displayName": "Retrieve Google Calendar Events",
  "description": "This tool allows you to fetch calendar events for a given user using the Google Calendar API.",
  "logoUrl": "path/to/your/logo.png",
  "auth": {
    "type": "NONE"
  }
}

Now we need to implement a server that Glean tools backend will call to fetch calender events. Note that the tool server should follow the guidelines listed in Defining a tool API spec. The three guidelines applicable here are -:

  1. The tool server should validate that the request is coming from Glean.
  2. Fields in the tool server api specification should not be nested.
  3. The tool server should use Glean-User-Email to fetch events on behalf of the user triggering the tool from Glean Assistant. The server should not allow users to get events by impersonating other users.

Here is how the server would look like in python -:

Copy
Copied
from flask import Flask, request, jsonify
from google.oauth2.service_account import Credentials
from googleapiclient.discovery import build
import json

app = Flask(__name__)

# Function to initialize Google Calendar API client
def get_calendar_service(user_email):
    credentials = Credentials.from_service_account_file(
        'path/to/service-account-file.json',
        scopes=['https://www.googleapis.com/auth/calendar'],
        subject=user_email
    )
    return build('calendar', 'v3', credentials=credentials)


@app.route('/calendar_events', methods=['GET'])
def get_calendar_events():
    # Extract user email from the header
    user_email = request.headers.get('Glean-User-Email')
    
    if not user_email:
        return jsonify({'error': 'Glean-User-Email header not found'}), 400
    
    service = get_calendar_service(user_email)
    
    time_max = request.args.get('timeMax')
    time_min = request.args.get('timeMin')
    query = request.args.get('query')
    
    try:
        events_result = service.events().list(
            calendarId='primary',
            timeMax=time_max,
            timeMin=time_min,
            q=query,
        ).execute()
        
        events = events_result.get('items', [])

        response_data = {
            'items': [
                {
                    'id': event['id'],
                    'htmlLink': event['htmlLink'],
                    'summary': event.get('summary', ''),
                    'description': event.get('description', ''),
                    'start': event['start'],
                    'end': event['end'],
                    'eventType': event.get('eventType', ''),
                    'attendees': event.get('attendees', []),
                    'creator': event.get('creator', {}),
                    'organizer': event.get('organizer', {})
                }
                for event in events
            ]
        }

        return jsonify(response_data), 200

    except Exception as e:
        print(f"An error occurred: {e}")
        return jsonify({'error': 'Something went wrong!'}), 400


if __name__ == '__main__':
    app.run(debug=True)

Finally, we need to define an API spec for the tool server. This API spec will be used by Glean Assistant to interact with the tool server, so we should make it as descriptive as possible. Here's how the API specification would appear for our Google Calendar events retrieval tool:

Copy
Copied
---
openapi: 3.0.0
info:
  title: Calendar Events Retrieval Tool
  version: 1.0.0
servers:
  - url: https://<your-base-url-here>
    variables:
      domain:
        default: api
        description: Domain for the API
paths:
  /calendar_events:
    get:
      summary: Retrieve calendar events
      description: |
        This API allows you to retrieve calendar events based on specified filters.
      parameters:
        - name: timeMax
          in: query
          schema:
            type: string
            format: date-time
          description: Upper bound (exclusive) for an event's start time.
        - name: timeMin
          in: query
          schema:
            type: string
            format: date-time
          description: Lower bound (exclusive) for an event's end time.
        - name: q
          in: query
          schema:
            type: string
          description: Free text search terms to find matching events.
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CalendarEventsResponse'
        '400':
          description: Bad Request
        '401':
          description: Not Authorized
        '404':
          description: Not Found
components:
  schemas:
    CalendarEventsResponse:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Event'
    Event:
      type: object
      properties:
        id:
          type: string
          description: Event ID.
        htmlLink:
          type: string
          description: An absolute link to this event in the Google Calendar Web UI.
        summary:
          type: string
          description: Event summary.
        description:
          type: string
          description: Event description.
        start:
          type: object
          properties:
            dateTime:
              type: string
              format: date-time
            timeZone:
              type: string
          description: Event start time.
        end:
          type: object
          properties:
            dateTime:
              type: string
              format: date-time
            timeZone:
              type: string
          description: Event end time.
        eventType:
          type: string
          description: Type of the event.
        attendees:
          type: array
          items:
            $ref: '#/components/schemas/CalendarUser'
          description: List of attendees.
        creator:
          $ref: '#/components/schemas/CalendarUser'
          description: Creator of the event.
        organizer:
          $ref: '#/components/schemas/CalendarUser'
          description: Organizer of the event.
    CalendarUser:
      type: object
      properties:
        id:
          type: string
          description: User ID.
        email:
          type: string
          format: email
          description: User email.
        displayName:
          type: string
          description: User display name.
Example of an execution tool that allows Glean Assistant to create Jira issues

First, you would need to create a tool manifest that describes basic properties of a tool. See Defining a tool manifest to learn more about creating a manifest for different types of tools. For an execution tool with OAuth based authentication, the manifest would look like this -:

Copy
Copied
{
  "type": "ACTION",
  "name": "CreateJiraIssue",
  "displayName": "Create Jira Issue Tool",
  "description": "This tool allows you to create a new issue in Jira. You can specify the project, issue type, and other details.",
  "enablePreview": true,
  "actionType": "EXECUTION",
  "logoUrl": "path/to/your/logo.png",
  "auth": {
    "type": "OAUTH_ADMIN",
    "client_url": "https://auth.atlassian.com/authorize?audience={ATLASSIAN-DOMAIN}.atlassian.net&prompt=consent",
    "scopes": [
      "write:jira-work",
      "offline_access",
      "read:me"
    ],
    "authorization_url": "https://auth.atlassian.com/oauth/token"
  }
}

Here we are using auth.type as OAUTH_ADMIN since jira allows using admin tokens to create issues using their cloud API on behalf of other users. For use cases, where each user needs to OAuth, you would need to use OAUTH_USER as auth.type.

Now we need to implement a server that Glean tools backend will call to create Jira issues. Note that the tool server should follow the guidelines listed in Defining a tool API spec. The three guidelines applicable here are -:

  1. The tool server should validate that the request is coming from Glean.
  2. Fields in the tool server api specification should not be nested.
  3. The tool server should use Glean-User-Email to fetch events on behalf of the user triggering the tool from Glean Assistant. The server should not allow users to get events by impersonating other users.

In Glean Assistant UI, users have the ability to authenticate themselves using OAuth for various tools. Glean also takes care of managing OAuth tokens for all users connected to these tools. Depending on the tool configuration, the backend of Glean sends the relevant OAuth token as a Bearer token in the Authorization header. For tools set up as OAUTH_ADMIN, the admin OAuth token is used. Conversely, for OAUTH_USER configured tools, the token of the user initiating the request from the Glean Assistant is used. The tool server can then use this token to interact seamlessly with Jira APIs.

Note: A distinct feature of this Jira issue creation tool is its handling of the reporter ID field. Unlike conventional setups, we aim to have the reporter ID always match the user who initiates the issue creation via the Glean Assistant interface. To achieve this, we utilize a specific header, Glean-User-Email, to fetch the appropriate reporter ID for inclusion in the issue creation request. This approach serves as an example of how we selectively prevent certain fields from being populated by large language models.

Here is how the server would look like in python -:

Copy
Copied
from flask import Flask, request, jsonify
import requests
import json

app = Flask(__name__)
cloud_id = "your_jira_cloud_id_here"
create_issue_url_format = f"https://api.atlassian.com/ex/jira/{cloud_id}/rest/api/3/issue"
users_search_url = f"https://your-domain.atlassian.net/rest/api/3/users/search"

def get_reporter_id(email, auth_header):
    start_at = 0
    max_results = 50
    while True:
        params = {'startAt': start_at, 'maxResults': max_results}
        headers = {'Accept': 'application/json', 'Authorization': auth_header}
        response = requests.get(users_search_url, params=params, headers=headers)
        users = json.loads(response.text)
        for user in users:
            if user['emailAddress'] == email:
                return user['accountId']
        if len(users) < max_results:
            break
        start_at += max_results
    return None

def transform_to_jira_request(input_payload, reporter_id):
    jira_request = {
        "fields": {
            "project": {"id": input_payload['pid']},
            "issuetype": {"id": str(input_payload['issuetype'])},
            "priority": {"id": str(input_payload['priority'])},
            "summary": input_payload['summary'],
            "components": [{"id": input_payload['components']}],
            "description": {
                "type": "doc",
                "version": 1,
                "content": [{"type": "text", "text": input_payload['description']}]
            },
            "assignee": {"id": input_payload['assignee']},
            "reporter": {"id": reporter_id}
        }
    }
    return jira_request

@app.route('/create_issue', methods=['POST'])
def create_issue():
    authorization_header = request.headers.get('Authorization')
    glean_user_email = request.headers.get('Glean-User-Email')
    if authorization_header is None or glean_user_email is None:
        return jsonify({"error": "Authorization header or Glean-User-Email not found"}), 401

    reporter_id = get_reporter_id(glean_user_email, authorization_header)
    if reporter_id is None:
        return jsonify({"error": "Reporter ID not found"}), 400

    input_payload = request.json
    jira_request = transform_to_jira_request(input_payload, reporter_id)

    headers = {
        "Content-Type": "application/json",
        "Authorization": authorization_header
    }

    response = requests.post(create_issue_url_format, headers=headers, json=jira_request)
    if response.status_code == 200:
        return jsonify({"resultURL": json.loads(response.text).get("key")}), 200
    else:
        return jsonify({"error": "Failed to create issue", "details": response.text}), response.status_code

if __name__ == '__main__':
    app.run(port=8080)

Finally, we need to define an API spec for the tool server. This API spec will be used by Glean Assistant to interact with the tool server, so we should make it as descriptive as possible. Here's how the API specification would appear for our create Jira issue tool:

Copy
Copied
---
openapi: 3.0.0
info:
  title: Jira Execution Tool
  version: 1.0.0
servers:
  - url: https://{domain}-be.glean.com/tools/jira
    variables:
      domain:
        default: domain
        description: Email domain (without extension) that determines the deployment backend.
paths:
  /create_issue:
    post:
      summary: Creates an issue or a sub-task from a JSON representation
      description: |
        This API allows you to create an issue in Jira.
      parameters:
        - name: Glean-User-Email
          in: header
          required: true
          schema:
            type: string
          description: Email of the authenticated glean user.
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                pid:
                  x-glean-typehint: 'JiraProjectID'
                  type: string
                  description: Project ID where the ticket is created.
                issuetype:
                  type: integer
                  description: The ID corresponding to the type of issue being created.
                priority:
                  type: integer
                  minimum: 1
                  maximum: 5
                  description: Numeric priority. 1 (highest) to 5 (lowest).
                summary:
                  x-glean-typehint: 'Content'
                  type: string
                  description: Title of the issue
                components:
                  x-glean-typehint: 'JiraComponent'
                  type: string
                  description: Component name where the ticket should be filed.
                description:
                  x-glean-typehint: 'Content'
                  type: string
                  description: Body of the issue.
                assignee:
                  x-glean-typehint: 'JiraId'
                  type: string
                  description: User to which the issue is assigned.
        required: true
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CreateIssuePostResponse'
        '400':
          description: Bad Request
        '401':
          description: Not Authorized
        '409':
          description: Conflict
components:
  schemas:
    CreateIssuePostResponse:
      type: object
      properties:
        resultURL:
          type: string
          description: URL of the created issue.
Example of a redirect action (using Redirect URL Builder) that allows Glean Assistant to create Zendesk tickets via redirecting to the Zendesk app

Begin by clicking “New Action” on the Actions settings page. The Redirect URL builder should appear as an option

Create new action

Like other tools, you will need to enter the display name and description, the unique identifier and the trigger conditions. Tool type has been auto-populated for you. You can enter the URL builder from the Functionality section.

The best way to start is to copy a URL into the target URL field. Let’s say we want to build a Zendesk ticket creation action. Zendesk uses the query parameters passed in the URL (the new ticket creation url in this case) to auto-populate fields in the new ticket creation experience. From their documentation, we know that the URL must look like this:

https://your-org.zendesk.com/hc/en-us/requests/new?tf_description=new+customer+request&tf_subject=new+customer

Once we do that, the Redirect URL builder will look like this. Notice that the parameter names have been auto filled, along with a single allowed value from the given URL. At this point, these parameters are considered constants because they take on only one possible value.

Redirect URL Builder

Of course, creating the exact same ticket every time is rarely useful. We want Glean Assistant to be able to understand and fill in the new ticket’s description and subject every time. To do this, simply delete the allowed value and add the relevant instructions for Glean Assistant on how to fill it in under Description for LLM. In addition, if you set a parameter to be "Required", Glean Assistant will ensure that it is filled when generating the final URL.

Redirect URL Builder

What if we want to add more parameters? We can do it either by clicking “+Add Query Parameter” link at the bottom of the table:

Add Query Parameter

Or by appending it directly to the URL:

Target URL

If we want to limit the value of a parameter to a set of values, simply place these values in the Allowed Values section (hit Enter for each value). It’s usually good practice to describe how Glean Assistant should select among these values in the Description section.

Redirect URL Builder

Beyond these, we also support path parameters. To add a path parameter, simply enclose the parameter name at the desired location within the path. You can then configure the parameter like before. Note that all path parameters must be Required parameters.

Redirect URL Builder

Example of a redirect tool (using API Spec) that allows Glean Assistant to create Jira issues via redirecting to Jira app

First, you would need to create a tool manifest that describes basic properties of a tool. See Defining a tool manifest to learn more about creating a manifest for different types of tools. For a redirect action tool, the manifest would look like this -:

Copy
Copied
{
  "type": "ACTION",
  "name": "JiraCreateTicket",
  "description": "This tool allows you to create a new issue in Jira. You can specify the project, issue type, and other details.",
  "enablePreview": true,
  "actionType": "REDIRECT"
}

In the case of redirect tools, there's no need to implement or host a server. Our role is solely to generate a URL that navigates the user directly to the resource creation interface within the target application — in our instance, the issue creation workflow in the Jira app. Glean Assistant takes care of pre-filling all required fields according to the API specification. Therefore, all we need to do is to create a comprehensive API spec to guide this process. Here's a sample spec for reference:

Copy
Copied
openapi: "3.0.0"
info:
  title: "Jira Create Ticket"
  version: "1.0.0"
servers:
  - url: "https://{YOUR-ATLASSIAN-DOMAIN}.atlassian.net/secure/CreateIssueDetails!init.jspa?"
paths:
  /:
    post:
      summary: "Creates an issue or a sub-task from a JSON representation"
      description: |
        This API allows you to create a ticket or an issue in Jira.
      parameters:
        - name: "pid"
          in: "query"
          required: true
          schema:
            type: "string"
            x-glean-typehint: "JiraProjectID"
            default: "10000"
          description: "Project ID where the ticket is created. 10000 refers to Engineering project."
        - name: "issuetype"
          in: "query"
          required: true
          schema:
            type: "integer"
            enum:
              - 10002
              - 10004
          description: "Issue type. 10002 refers to Task, 10004 refers to Bug."
        - name: "priority"
          in: "query"
          schema:
            type: "integer"
            minimum: 1
            maximum: 5
          description: "Numeric priority. 1 (highest) to 5 (lowest)."
        - name: "summary"
          in: "query"
          required: true
          schema:
            type: "string"
            x-glean-typehint: "Content"
          description: "Title of the issue."
        - name: "components"
          in: "query"
          schema:
            type: "string"
            x-glean-typehint: "JiraComponent"
          description: "Component name where the ticket should be filed."
        - name: "description"
          in: "query"
          schema:
            type: "string"
            x-glean-typehint: "Content"
          description: "Body of the issue."
        - name: "assignee"
          in: "query"
          schema:
            type: "string"
            x-glean-typehint: "JiraId"
          description: "User to which the issue is assigned."
responses:
  '200':
    description: "OK"
    content:
      application/json:
        schema:
          type: "string"

One thing to note here is that the API specification includes illustrative examples in the parameter descriptions, such as specifying that a pid of "10000" corresponds to the Engineering project or that "10002" and "10004" refer to Task and Bug issue types, respectively. Examples like these enable Glean Assistant to accurately pre-fill the fields.

This is what it would look like on the Glean Assistant UI -: