Early access

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 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"
  },
  "objectName": "Chat message"
}

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=q
        ).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 tool 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 -: