Only this pageAll pages
Powered by GitBook
1 of 26

BDK

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Planning Guide

A checklist to guide the planning of your BDK Book project, covering key requirements and considerations.

Software Requirements

Ensure you have access to the following software requirements.


Book Design

Books are collections of procedures that define specific actions within an automation. Each represents an automation task or step contributing to a larger process.

Designing a Book involves defining these procedures to ensure they align with the automation’s objectives. To guide this process, take into account the following considerations:

1. Functionality

Identify the specific procedures needed for the Book. Each procedure should have a clear and specific automation purpose.

Questions

  • What actions does the user need to automate in Kognitos?

  • Can the actions be broken down into individual steps?

Examples

Consider these procedures for a Weather Book:

  • Get the current temperature in a specific city.

  • Check the current air quality index (AQI).

  • Determine delays or disruptions caused by severe weather for shipping and transportation.

  • Notify attendees of weather-related changes for events.

2. Data

Clearly define each procedure's inputs, outputs, and processing steps.

Questions

  • What input or parameters are needed for each procedure?

  • What output or data should each procedure return upon completion?

  • How will data be transformed, manipulated, or processed in each procedure?

Examples

Consider an procedure that gets the temperature in a specific city:

3. Error Handling

Consider common error scenarios and define how they will be handled.

Questions

  • What are the potential error scenarios that could occur?

  • How will these errors be handled and/or logged?

Examples

Consider the following error scenarios for a Weather Book:

1. Invalid Input

  • A user enters a non-existent city name (e.g., "Atlantis").

  • The system receives a blank input for the city name.

2. API Unavailability

  • The weather API is temporarily down or unreachable.

  • A network timeout occurs during an API call.


Integrations

If you plan to integrate with an external API in your Book, it’s important to assess the target service's requirements, limitations, and authentication mechanisms. The following considerations will ensure your chosen API aligns with your automation needs and integrates seamlessly into your BDK project.

1. Authentication & Authorization

Understand the authentication requirements for your target integration. BDK supports standard API authentication methods, including API keys, OAuth 2.0, client credentials, etc.

Questions

  • Does the API require authentication? If so, what kind?

  • Are there specific permissions or roles required to access certain API endpoints?

  • Can you obtain the necessary access and permissions for all required credentials?\

2. Functionality & Data Flow

Identify which API endpoints will be used and define the data that will be exchanged.

Questions

  • Which API endpoints will your Book use?

  • What data will be sent or received?

3. Rate Limits and Usage

Review the API’s rate limits and usage quotes. Ensure they can handle your expected request volume and check for additional costs or restrictions.

Questions

  • Are there API rate limits or usage quotes?

  • Does the API’s rate limit support the expected request volume for your workflows?

  • Are there additional costs or restrictions for exceeding usage limits?

4. Documentation and Support

Evaluate if the API documentation is clear and if provider support is accessible.

Questions

  • Is the API documentation comprehensive and easy to follow?

  • Does the API provider offer support?

5. Scalability

Plan for future scalability and consider how the API will handle growing request needs.

Questions

  • Can the API handle increasing request volumes as workflows grow?

  • Does the API support additional functionality that might be required in future Books?


Deployment

Prepare to deploy and manage a Docker container

Considerations

  1. Ensure the required infrastructure is in place to deploy a Docker container in the cloud, on-premises, or in a hybrid environment.

  2. Plan strategies for:

    1. Managing the container lifecycle, including scaling, networking, and security.

    2. Implementing logging to track container activity

Monitoring container health and performance

  • Updating Docker images for new releases with clear versioning and testing practices.

  • Input

    • City name

    Output

    • Temperature (°F)

    • Timestamp when the temperature was recorded

    Processing Steps

    1. Validate the city name.

    2. Use an API request to get weather data for the specified city.

    3. Parse the response to extract the temperature value and timestamp.

    4. Format the temperature reading to the appropriate unit (Fahrenheit).

    GitHub Account
    Docker
    Cookiecutter
    Python 3.11+

    Enums

    Decorators

    Connection Commands

    Learn about writing connection commands for Books built with BDK.

    You must first learn your Book before connecting it.

    Overview

    A connection command is written in Kognitos to instantiate a connection to a third-party tool or service from a BDK Book. Whenever a Book implements a connection using the decorator, a connection command is required in your automation process.

    Connection commands are also required when calling a from a BDK Book that requires a connection. Procedures requiring a connection will have connection_required=True in the decorator.

    Syntax

    Use the following syntax to write a connection command in a Kognitos automation:

    Components

    • {book name} - The name of your Book, as defined in your decorator.

    • {noun phrase} – The noun_phrase defined in your decorator.

    • {preposition} – Any preposition that aligns with the plurality of the noun phrase. For example:

    Passing Arguments

    To pass arguments to your connection, add the with keyword at the end of the syntax. List each argument on a separately indented line, using the following format:

    Examples

    1. Without Connection Arguments

    2. Using Connection Arguments

    In this example, api keys is the noun phrase, and several connection arguments are provided.

    FilterUnaryOperator

    Enum Definition

    from enum import Enum
    
    class FilterUnaryOperator(Enum):
        """Represents the unary operators used in filtering procedures."""
        NOT = 0

    Enum Members

    Member
    Value
    Description

    NOT

    0

    Logical NOT operator, negates a condition.

  • Plural noun phrase: via some API keys

  • Singular noun phrase: via a client credentials method

  • @connect
    procedure
    @procedure
    @book
    @connect
    connect {book name} via {preposition} {noun phrase}
    connect {book name} via {preposition} {noun phrase} with
      the {argument 1} is "{value 1}"
      the {argument 2} is "{value 2}"
      the {argument 3} is "{value 3}"
    connect grimoire via the magic method
    connect photon via some api keys with
      the api key is "1234-5678-ABCD-EFGH"
      the client id is "client-98765"
      the secret key is "s3cr3tK3y!"
      the username is "photon_user"
      the password is "P@ssw0rd!"

    @config

    Overview

    The config decorator is used to decorate a property in a Book class that defines default configuration values.

    Syntax

    @config(*args, **kwargs)

    Keyword Arguments

    Argument
    Type
    Required
    Description

    Example

    @connect

    Overview

    The @connect decorator is used to wrap functions that handle or authentication tasks. It defines the syntax for writing a , which instantiates the connection in Kognitos to the third-party API or service that's implemented in your Python function.

    @book

    Overview

    The @book decorator is used to identify a class as a Book.

    Syntax

    @oauthtoken

    Overview

    The @oauthtoken decorator marks a function as handling OAuth tokens, enabling the function to be automatically recognized as part of an OAuth workflow.

    Syntax

    Connections

    Learn how to connect with third-party tools and services in your BDK project.

    Implementing Connections

    Connections enable you to connect to third-party tools and services in your custom Book. To implement a connection, define a Python function in your Book class and decorate it with the decorator. This decorated function serves as the connection handler and defines the syntax for writing a .

    Noun Phrases

    Learn about the concept of a noun phrase in the BDK.

    Overview

    A noun phrase is a group of words centered around a noun. It consists of:

    • Head: The main noun representing the fact.

    FilterBinaryOperator

    Enum Definition

    Enum Members

    Member

    name

    str

    Optional

    Specifies the name of the configuration. If not provided, the function name or value from *args is used.

    default_value

    Any

    Optional

    Specifies the default value for the configuration. If not provided, the default is None.

    Syntax

    Keyword Arguments

    Argument
    Type
    Description

    name

    str

    A name to associate with the connection. If not provided, it defaults to the function name.

    noun_phrase

    str

    A unique label to identify the authentication method. If not provided, it will be inferred from the function name. Examples include, but are not limited to:

    • noun_phrase="api keys"

    • noun_phrase="client credentials"

    • noun_phrase="user password authentication"

    Implementation Example

    This is an example implementation of connecting to the OpenWeather API using an API key:

    Refer to connections for additional context and usage examples.

    connections
    connection command

    Note: This decorator always applies to the same method signature, but the method itself can have any name.

    Example

    @oauthtoken
    def function_name(self, access_token: str, expires_in: Optional[int] = None) -> None:
    Note: Implementing an OAuth connection in a custom BDK Book is not supported at this time.

    Multiple Connections

    You can define multiple connections within a Book, each with its own handler method. Use the noun_phrase keyword argument in the @connect decorator to assign a unique label to each connection and differentiate between authentication methods.

    Method Docstrings

    In your connection method docstring, include the following sections in addition to a brief summary:

    1. Arguments

    Specify the Python function's parameters.

    2. Labels

    Define labels for the connection arguments (e.g., API key, credentials) that will be used in your connection command. Labels correspond to parameters in your function's definition.

    When a label is provided in the docstring, the lowercase form is used in a connection command. When not provided, the label will be inferred from the Python variable name.

    Example

    In this example, API Key is the label for the api_key connection argument. The lowercase version of the label is used in a connection command (api key):

    Additional Examples

    1. api_key: ApI kEy

    In this example, the connection command uses the lowercase form of ApI kEy:

    1. api_key: aPi_KeY

    In this example, the connection command uses the lowercase version of aPi_KeY:

    1. No Label

    If a label is not provided in the docstring, it is inferred from the Python variable name, api_key:

    @connect
    connection command
    Modifiers: Optional words providing additional context to the head noun.

    The NounPhase class can be used to represent a noun phrase.

    Modifiers

    A modifier is an optional part of a noun phrase that provides additional detail about the head noun. It appears before the head and refines the fact being referenced.

    Example: Creating a Noun Phrase with Modifiers

    In this example:

    • Head: "dog"

    • Modifiers: "big", "white"

    The resulting noun phrase is "big white dog".


    Parameters as Noun Phrases

    Parameters can be defined as NounPhrase types to tell the system to use a fact's name instead of its value.

    Example: Defining a Parameter as a Noun Phrase

    Consider the following procedure method definition, where the city parameter is defined as a NounPhrase:

    This procedure can be called in an automation where London is a fact with a specific value:

    In this example, when London is specified as the city parameter, the system uses the name of the fact (London) instead of its value ("windy").

    DEFAULT_TIMEOUT = 30
    
    @property
    @config(default_value=DEFAULT_TIMEOUT)
    def timeout(self) -> float:
     """
     Timeout in seconds when making API calls.
     """
     return self._timeout
    
    @timeout.setter
    def timeout(self, timeout: float):
     """
     Sets the timeout value in seconds.
     """
     if timeout <= 0:
      raise ValueError("timeout must be positive")
     self._timeout = timeout
    @connect(*args, **kwargs)
    @connect(noun_phrase="api keys")
    def connect(self, api_key: str):
      """
      Authenticate to Open Weather API using the specified API key. 
      You can obtain your own API key by visiting Open Weather's 
      website at https://openweathermap.org/appid.
    
      Arguments:
          api_key: The API key to be used for connecting
    
      Labels:
          api_key: API Key
      """
      api_key = os.getenv("API_KEY", api_key)
      test_url = f"{self._base_url}?appid={api_key}&q=London"
      response = requests.get(test_url, timeout=self._timeout)
      if response.status_code == 401:
          response_data = response.json()
          if "Invalid API key" in response_data.get("message", ""):
              raise ValueError("Invalid API key")
    
      self._api_key = api_key
    @oauth(
        id="oauth",
        provider=OAuthProvider.MICROSOFT,
        flows=[OAuthFlow.AUTHORIZATION_CODE],
        authorize_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize",
        token_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
        scopes=[
            "https://graph.microsoft.com/Files.ReadWrite",
            "https://graph.microsoft.com/Sites.ReadWrite.All",
        ],
    )
    @book(name="MicrosoftBook")
    class BaseMicrosoftBook:
        """
        Base class for all Microsoft Books.
    
        OAuth Arguments:
            TENANT_ID: The tenant ID of the Azure AD directory.
    
        OAuth Labels:
            TENANT_ID: Tenant ID
        """
    
        _acquired_token: Optional[AcquiredToken] = None
        _headers: Optional[dict] = None
    
        def __init__(self):
            """
            Initializes an instance of the class.
            """
            self._acquired_token = None
            self._headers = None
    
        @oauthtoken
        def handle_token(self, access_token: str, expires_in: Optional[int] = None) -> None:
            """
            Handles the OAuth token.
    
            Args:
                access_token (str): The OAuth access token.
                expires_in (Optional[int]): The expiration time of the token, in seconds.
    
            Labels:
                access_token: OAuth Access Token
                expires_in: Token Expiration Time
            """
            self._acquired_token = AcquiredToken(access_token, expires_in or 3600)
    
    @connect(noun_phrase="api keys")
    def connect(self, api_key: str):
        """
        Authenticate to Open Weather API using the specified API key. You can obtain your own API key by visiting
        Open Weather's website at https://openweathermap.org/appid.
    
        Arguments:
            api_key: The API key to be used for connecting
    
        Labels:
            api_key: API Key
        """
    
        api_key = os.getenv("API_KEY", api_key)
        test_url = f"{self._base_url}?appid={api_key}&q=London"
        response = requests.get(test_url, timeout=self._timeout)
        if response.status_code == 401:
            response_data = response.json()
            if "Invalid API key" in response_data.get("message", ""):
                raise ValueError("Invalid API key")
    
        self._api_key = api_key
    connect openweather via some api keys method with
        the api key is "aBcDefg1234"
    connect openweather via some api keys method with
        the api key is "aBcDefg1234"
    connect openweather via some api keys method with
        the api_key is "aBcDefg1234"
    connect openweather via some api keys method with
        the api key is "aBcDefg1234"
    # Create a noun phrase with a head and modifiers
    np = NounPhrase("dog", ["big", "white"])
    print(np)  # Outputs: big white dog
    @procedure("to get the (current temperature) at a city")
    def current_temperature(
      self, city: NounPhrase, unit: Optional[NounPhrase] = NounPhrase("metric")
    ) -> float:
    London is "windy"
    get the current temperature at London
    Keyword Arguments
    Argument
    Type
    Description

    id

    str

    A unique identifier for the Book.

    icon

    str

    Path to an icon representing the Book. If not provided, a default icon will be used.

    name

    str

    The name of the Book. If not provided, the name will be inferred from the class name.

    noun_phrase

    str

    The book noun phrase.

    Default Icon

    If an icon is not specified, this will be used as the default.

    The default icon for a BDK Book.

    Example

    Value
    Description

    AND

    0

    Logical AND operator, used to combine conditions.

    OR

    1

    Logical OR operator, used to combine alternative conditions.

    EQUALS

    2

    Tests if two values are equal.

    NOT_EQUALS

    3

    Tests if two values are not equal.

    IN

    4

    Tests if a value is within a specified list or range.

    HAS

    @oauth

    Overview

    The @oauth decorator is used to apply OAuth-based authentication to classes. It adds OAuth-specific metadata, such as endpoints, arguments, and provider details, to the class, enabling integration with OAuth services.

    Syntax

    Parameters

    Parameter
    Type
    Required
    Description

    Example

    Procedures

    Learn how to implement a procedure with the BDK.

    Implementing Procedures

    To implement a procedure in a BDK project, define a Python function in your Book class and decorate it with the @procedure decorator.

    Requirements

    1. Naming

    Ensure the name of your procedure adheres to the syntax guidelines specified in the name parameter of the decorator.

    2. Method Docstrings

    Your method should include the following sections:

    • A brief summary of the procedure

    • Input Concepts

    • Output Concepts

    Examples are not required but are valuable for generating usage documentation.

    3. Concept-Parameter Matching

    Concepts and parameters must match to ensure they are properly mapped internally. Ensure your method definition adheres to the .

    Refer to for examples of procedure implementations.


    Using Procedures in Your Automation

    Singularized Calls

    A singularized call is a way to call a procedure by phrasing it as if it returns a single item, even though it returns a list by definition. A procedure supports singularized calls in addition to standard calls if it meets all of the following conditions:

    • Returns a list.

    • The output of the procedure is the object itself.

    • The output noun phrase is plural.

    • The procedure accepts filters.

    You don't need to implement additional logic for singular calls. BDK will automatically generate the singularized variation of any procedure that meets the above criteria.

    Example

    Consider a procedure that retrieves users from Outlook. It can be called in two ways:

    • Standard Way: get some users from outlook whose whose mail is "example.com"

    • Singularized Way: get a user from outlook whose whose mail is "example.com"

    @book(*args, **kwargs)
    class BookClass:
       """
        A sample book. 
    
        Author:
          Kognitos
        """
        # Book implementation here
    @book(name="Twilio", icon="data/twilio.svg")
    class TwilioBook:
       """
        The Twilio Book enables users to interact with the Twilio API.
        """
        ...
    from enum import Enum
    
    class FilterBinaryOperator(Enum):
        """Represents the different binary operators used in filtering procedures."""
        AND = 0
        OR = 1
        EQUALS = 2
        NOT_EQUALS = 3
        IN = 4
        HAS = 5
        LESS_THAN = 6
        GREATER_THAN = 7
        LESS_THAN_OR_EQUAL = 8
        GREATER_THAN_OR_EQUAL = 9

    5

    Tests if a collection contains a specific element.

    LESS_THAN

    6

    Tests if one value is less than another.

    GREATER_THAN

    7

    Tests if one value is greater than another.

    LESS_THAN_OR_EQUAL

    8

    Tests if one value is less than or equal to another.

    GREATER_THAN_OR_EQUAL

    9

    Tests if one value is greater than or equal to another.

    BDK API Reference

    The BDK API is the core Python library in the Book Development Kit (BDK). It enables developers to build a Book. This reference covers the essential elements for working with the BDK API.

    authorize_endpoint

    str

    Yes

    The authorization endpoint URL for the OAuth provider.

    token_endpoint

    str

    Yes

    The token endpoint URL for exchanging the authorization code for a token.

    scopes

    List[str]

    Optional

    A list of scopes required by the OAuth provider for the authenticated requests.

    id

    str

    Yes

    The unique identifier for the OAuth configuration.

    provider

    OAuthProvider

    Yes

    The OAuth provider:

    • OAuthProvider.MICROSOFT

    • OAuthProvider.GOOGLE

    flows

    List[OAuthFlow]

    Optional

    A list of OAuth flows.

    @procedure
    docstring
    guidelines
    this section
    @oauth(
        id=str,
        provider=OAuthProvider,
        flows=Optional[List[OAuthFlow]] = None,
        authorize_endpoint=str,
        token_endpoint=str,
        scopes=Optional[List[str]] = None,
    )
    class ClassName:
        """
        Class description
        """
        # Class implementation
    @oauth(
        id="oauth",
        provider=OAuthProvider.MICROSOFT,
        flows=[OAuthFlow.AUTHORIZATION_CODE],
        authorize_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize",
        token_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
        scopes=[
            "https://graph.microsoft.com/Files.ReadWrite",
            "https://graph.microsoft.com/Sites.ReadWrite.All",
        ],
    )
    @book(name="MicrosoftBook")
    class BaseMicrosoftBook
    @procedure("to get some (users) from *office365*")
    async def retrieve_users(self, filter_expression: Optional[FilterExpression] = None) -> List[OfficeUser]:
      """
      Retrieves Office 365 users accessible via the Microsoft Graph API.
    
      It requires the following permissions on the application:
      User.Read.All, User.ReadWrite.All, Directory.Read.All, Directory.ReadWrite.All
    
      Returns:
      A list of Office 365 users.
    
      Example 1:
      Retrieve all users
    
      >>> get users from office365
    
      Example 2:
      Retrieve a user whose email matches the specified email address
    
      >>> get users from office365 whose mail is "[email protected]"
      """

    BDK Overview

    An overview of the Book Development Kit (BDK).

    What is the BDK?

    The Book Development Kit (BDK) is a software toolkit used to develop books for Kognitos. are collections of related automation procedures (tasks). They can be added to an agent to expand automation functionality and integrate with external tools.

    Although Kognitos builds and maintains a of books to address common use cases, there may be situations that require a custom book. Using the BDK, you can develop, deploy, and connect your own book to help with:

    1. Integrations

    With the BDK, you can create procedures that interact with third-party APIs, databases, and external services. If your organization uses tools or services not natively integrated with Kognitos, you can build a custom Book to integrate them directly into your automation workflows.

    2. Extending Core Functionalities

    Extend Kognitos’ features to support more complex scenarios beyond standard functionality. Whether you need to support advanced data manipulation or custom data validation, the BDK provides the tools to create custom Books that meet your unique requirements.

    BDK Components

    The BDK is comprised of the following software components:

    Component
    Description

    Book Development Lifecycle

    The Book Development Lifecycle outlines the stages of software development for a new Book and how the BDK components interact throughout the process.

    1. Planning

    The planning phase is where the Book's functionality is defined. In this stage, you identify the external systems the Book will integrate with and outline the workflows or automation goals. This stage clarifies objectives and requirements, setting the foundation for development.

    Refer to our to help you plan your project.

    2. Development

    The development phase is where the Book’s functionality is implemented using the BDK API and other tools:

    • The BDK Book Template provides starter code to streamline setup.

    • The BDK Linter enforces coding standards and identifies potential issues.

    • The BDK Poetry Plugin manages dependencies, versioning, and documentation.

    3. Testing

    The testing phase ensures that Books perform as intended. The BDK Book Template includes a testing framework for validating Book functionality.

    4. Packaging & Deployment

    In this stage, the Book is packaged and prepared for deployment. The code is packaged with the BDK Runtime using Docker and deployed externally.

    5. Execution

    Once deployed, the Book is ready to run in Kognitos, enabling you to extend your automations with custom workflows.

    Resources

    Example Books

    Setup Guide

    Learn how to set up your Book project with the BDK.

    Prerequisites

    Ensure the following prerequisites are met and dependencies are installed before proceeding.

    1. Python 3.11+

    pyenv (recommended)

    Pyenv manages Python versions and project dependencies with the pyenv-virtualenv plugin.

    1. Follow the installation steps for .

    2. Follow the installation steps for .

    Python can be installed with pyenv in the following way:

    2.

    3.

    4.

    5.

    6.


    Setting Up Your Project

    Set up your project using the BDK Template, a tool designed to simplify the creation of Books.

    1. Clone and Initialize

    Clone the from GitHub and initialize your project with Cookiecutter:

    Enter the following project details or press Enter to keep the defaults:

    Configuration
    Description
    Default

    2. Enter Project Directory

    Navigate into the new project directory. Replace project_slug with your own Project Slug from the previous step:

    3. Create a Virtual Environment (recommended)

    We recommend creating a virtual environment to isolate and manage your project dependencies.

    1. to create the virtual environment inside the project’s root directory:

    2. Create a virtual environment:

    Setting your local pyenv version

    If you are using pyenv and encounter the following error:

    Run the following command to set the local pyenv version for the current directory:

    3. Activate the virtual environment

    Run poetry shell or manually using the following commands:

    Note: To later exit the virtual environment later, run deactivate.

    4. Install dependencies

    Deployment

    Learn the requirements for deploying a BDK Book.

    After successfully building your Book and creating a Docker image, the next step is deployment. You have the flexibility to deploy your Book to any environment that supports external access and routing, ensuring it can be reached from Kognitos. The choice of deployment method will depend on your organization's specific preferences and requirements. Common deployment options include:

    • Cloud platforms (ex: Amazon ECS, Azure)

    • On-premises infrastructure

    • Container orchestration platforms, such as Kubernetes

    Port Configuration

    Ensure that the port specified in the Dockerfile is properly exposed and matches your deployment environment's configuration.

    Refer to our for additional deployment considerations.

    @concept

    Overview

    The @concept decorator designates a class as a , allowing it to be used as a custom data type for defining procedure inputs and outputs.

    Syntax

    Filter Expressions

    Learn about implementing filter expressions in your BDK project.

    Overview

    Filter expressions enable you to define filter criteria for your procedures using the whose keyword. These expressions allow you to filter data based on conditions such as equality, comparisons, and more. For example:

    Planning Guide

    Initial Version

    Initial project version.

    0.1.0

    Project Name

    The name of your project.

    Sample Book

    Project Slug

    A URL-friendly version of the project name. If not provided, this will be derived from the project name.

    sample_book

    Project Description

    A short description of the project.

    A short description of the project.

    Author Name

    You or your organization's name.

    pyenv
    pyenv-virtualenv
    pyenv install 3.11
    Git
    Docker
    pipx
    Poetry
    Cookiecutter
    BDK Template
    Configure Poetry
    activate the environment

    Kognitos

    Keyword Arguments
    Argument
    Types
    Description

    is_a

    str, List[str]

    The name of the custom concept to be referred to in the Kognitos platform.*

    unset

    Any

    Specifies a default value to represent an unset state for the concept.

    *In the case of a List, each element would correspond to a noun phrase (a noun and its adjectives) in the hierarchy of possessives. For example:

    is_a=["red dragon", "big house", "wooden door"] = red dragons's big house's wooden door

    Example

    The following example demonstrates the usage of the @concept decorator to define the custom concept sms message:

    concept
    Implementation

    1. Include the filter_expression parameter

    To implement a filter expression, you need to provide the special filter_expression parameter in your procedure method definition. For example:

    2. Implement Visitors

    The FilterExpressionVisitor class is an abstract base class that defines methods for visiting different types of filter expressions. Each type of filter expression (binary, unary, value, noun phrase) is defined as a subclass of FilterExpression. You will need to define a class that implements these methods to handle filter expressions. For example:

    Below is an example implementation of the FilterExpressionVisitor class in the Twilio Book:

    3. Processing Filter Expressions

    Once you’ve defined your visitor class, you need to pass an instance of it to the filter expression. This is done by calling accept onfilter_expression. For example:


    Example

    In this example, a filter expression is used in the read some SMS messages procedure:

    cookiecutter git+ssh://[email protected]/kognitos/bdk-template.git
    cd <project_slug>
    poetry config virtualenvs.in-project true
    poetry env use 3.11
    pyenv: python3.11: command not found
    
      The python3.11' command exists in these Python versions:
       3.11.11
    pyenv local 3.11
    source $(poetry env info --path)/bin/activate
    Invoke-Expression (poetry env activate)
    poetry install
    @concept(*args, **kwargs)
    @concept(is_a="sms message")
    @dataclass
    class SMSMessage:
        """
        An SMS (Short Message Service) message. Represents a text communication sent over a cellular network,
        typically between mobile phones.
    
        Attributes:
            body: The text content of the message
            num_segments: The number of segments that make up the complete message. SMS message bodies that exceed
                the [character limit](https://www.twilio.com/docs/glossary/what-sms-character-limit) are segmented
                and charged as multiple messages. Note: For messages sent via a Messaging Service, `num_segments` is
                initially `0`, since a sender hasn't yet been assigned
            sender_number: The sender's phone number (in [E.164](https://en.wikipedia.org/wiki/E.164) format),
                [alphanumeric sender ID](https://www.twilio.com/docs/sms/quickstart),
                [Wireless SIM](https://www.twilio.com/docs/iot/wireless/programmable-wireless-send-machine-machine-sms-commands),
                [short code](https://www.twilio.com/en-us/messaging/channels/sms/short-codes), or
                [channel address](https://www.twilio.com/docs/messaging/channels) (e.g., `whatsapp:+15554449999`). For
                incoming messages, this is the number or channel address of the sender. For outgoing messages, this value
                is a Twilio phone number, alphanumeric sender ID, short code, or channel address from which the
                message is sent
            recipient_number: The recipient's phone number (in [E.164](https://en.wikipedia.org/wiki/E.164) format) or
                [channel address](https://www.twilio.com/docs/messaging/channels) (e.g. `whatsapp:+15552229999`)
            date_updated: The [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) timestamp (in GMT) of
                when the Message resource was last updated
            price: The amount billed for the message in the currency specified by `price_unit`. The `price` is populated
                after the message has been sent/received, and may not be immediately availalble. View
                the [Pricing page](https://www.twilio.com/en-us/pricing) for more details.
            account_sid: The SID of the [Account](https://www.twilio.com/docs/iam/api/account) associated with
                the Message resource
            num_media: The number of media files associated with the Message resource.
            status: The status of the message, for more information about possible statuses see
                [Message Status](https://www.twilio.com/docs/messaging/api/message-resource#message-status-values)
            messaging_service_sid: The SID of the [Messaging Service](https://www.twilio.com/docs/messaging/api/service-resource)
                associated with the Message resource. A unique default value is assigned if a Messaging Service is not used.
            sid: The unique, Twilio-provided string that identifies the Message resource.
            date_sent: The [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) timestamp (in GMT) of when
                the Message was sent. For an outgoing message, this is when Twilio sent the message. For an
                 incoming message, this is when Twilio sent the HTTP request to your incoming message webhook URL.
            date_created: The [RFC 2822](https://datatracker.ietf.org/doc/html/rfc2822#section-3.3) timestamp (in GMT) of
                when the Message resource was created
            price_unit: The currency in which `price` is measured, in
                [ISO 4127](https://www.iso.org/iso/home/standards/currency_codes.htm) format (e.g. `usd`, `eur`, `jpy`).
            error_message: The description of the `error_code` if the Message `status` is `failed` or `undelivered`. If no
                error was encountered, the value is `null`.
            error_code: The [error code](https://www.twilio.com/docs/api/errors) returned if the Message `status` is
                `failed` or `undelivered`. If no error was encountered, the value is `null`.
        """
    
        sid: Optional[str]
        body: Optional[str]
        num_segments: Optional[str]
        sender_number: Optional[str]
        recipient_number: Optional[str]
        price: Optional[float]
        account_sid: str
        num_media: int
        status: Optional[str]
        messaging_service_sid: str
        date_sent: Optional[datetime]
        date_created: Optional[datetime]
        date_updated: Optional[datetime]
        price_unit: str
    
        error_code: Optional[int]
        error_message: Optional[str]
    
        @classmethod
        def from_message_instance(cls, message_instance: MessageInstance) -> Self:
            """
            Constructs an SMSMessage object from a given MessageInstance object.
    
            Arguments:
                message_instance (MessageInstance): The MessageInstance object to create the SMSMessage from.
    
            Returns:
                The newly created SMSMessage object.
    
            """
            return SMSMessage(
                sid=message_instance.sid,
                body=message_instance.body,
                num_segments=message_instance.num_segments,
                sender_number=message_instance.from_,
                recipient_number=message_instance.to,
                date_updated=message_instance.date_updated,
                price=message_instance.price,
                status=message_instance.status,
                messaging_service_sid=message_instance.messaging_service_sid,
                date_sent=message_instance.date_sent,
                date_created=message_instance.date_created,
                account_sid=message_instance.account_sid,
                num_media=message_instance.num_media,
                price_unit=message_instance.price_unit,
                error_code=message_instance.error_code,
                error_message=message_instance.error_message,
            )
    get users from office365 whose email is "[email protected]"
    @procedure("to read some (*SMS* messages)")
    def read_sms_messages(
        self,
        offset: Optional[int],
        limit: Optional[int],
        filter_expression: Optional[FilterExpression],
    ) -> List[SMSMessage]:
        """
        Read some SMS messages using the Twilio API.
        """
    class MyFilterVisitor(FilterExpressionVisitor):
        def visit_binary_expression(self, expression: FilterBinaryExpression) -> T:
            # Implement logic for handling binary expressions
            pass
    
        def visit_unary_expression(self, expression: FilterUnaryExpression) -> T:
            # Implement logic for handling unary expressions
            pass
    
        def visit_value(self, expression: ValueExpression) -> T:
            # Implement logic for handling value expressions
            pass
    
        def visit_noun_phrases(self, expression: NounPhrasesExpression) -> T:
            # Implement logic for handling noun phrases expressions
            pass
    SENDER_NUMBER = NounPhrase.from_word_list(["sender", "number"])
    RECIPIENT_NUMBER = NounPhrase.from_word_list(["recipient", "number"])
    DATE_SENT = NounPhrase.from_word_list(["date", "sent"])
    
    
    class SMSMessageFilter(FilterExpressionVisitor):
        """
        Extract filtering information from the expression
        """
    
        current_noun_phrase: Optional[NounPhrase] = None
        current_value: Optional[Any] = None
    
        recipient_number: Union[str, object] = values.unset
        sender_number: Union[str, object] = values.unset
        date_sent: Union[datetime, object] = values.unset
        date_sent_before: Union[datetime, object] = values.unset
        date_sent_after: Union[datetime, object] = values.unset
    
        def visit_binary_expression(self, expression: FilterBinaryExpression):
            expression.left.accept(self)
            expression.right.accept(self)
    
            if expression.operator == FilterBinaryOperator.EQUALS:
                if self.current_noun_phrase == SENDER_NUMBER:
                    self.sender_number = str(self.current_value)
                elif self.current_noun_phrase == RECIPIENT_NUMBER:
                    self.recipient_number = str(self.current_value)
                elif self.current_noun_phrase == DATE_SENT:
                    if not isinstance(self.current_value, datetime):
                        raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)
    
                    self.date_sent = self.current_value
            elif expression.operator == FilterBinaryOperator.GREATER_THAN:
                if self.current_noun_phrase == DATE_SENT:
                    if not isinstance(self.current_value, datetime):
                        raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)
    
                    self.date_sent_after = self.current_value
            elif expression.operator == FilterBinaryOperator.LESS_THAN:
                if self.current_noun_phrase == DATE_SENT:
                    if not isinstance(self.current_value, datetime):
                        raise TypeMismatchError("date_sent", ConceptScalarType.DATETIME)
    
                    self.date_sent_before = self.current_value
            elif expression.operator == FilterBinaryOperator.AND:
                pass
            else:
                raise ValueError(f"unsupported filtering operator: {expression.operator}")
    
        def visit_unary_expression(self, expression: FilterUnaryExpression):
            pass
    
        def visit_value(self, expression: ValueExpression):
            self.current_value = expression.value
    
        def visit_noun_phrases(self, expression: NounPhrasesExpression):
            if len(expression.noun_phrases) != 1:
                raise ValueError(
                    f"unsupported filtering noun phrase: {expression.noun_phrases}"
                )
    
            if expression.noun_phrases[0] not in [
                SENDER_NUMBER,
                RECIPIENT_NUMBER,
                DATE_SENT,
            ]:
                raise ValueError(
                    f"unsupported filtering noun phrase: {expression.noun_phrases}"
                )
    
            self.current_noun_phrase = expression.noun_phrases[0]
    visitor = MyFilterVisitor()
    filter_expression.accept(visitor)
    @procedure("to read some (*SMS* messages)")
    def read_sms_messages(
        self,
        offset: Optional[int],
        limit: Optional[int],
        filter_expression: Optional[FilterExpression],
    ) -> List[SMSMessage]:
        """
        Read some SMS messages using the Twilio API.
    
        Returns:
            A list of SMS messages that matches the specified filtering criteria
    
        Example 1:
            Retrieve SMS messages filtered by sender and recipient numbers
    
            >>> read some sms messages whose sender number is "+18004445555" and whose recipient number is "+18004446666"
    
        Example 2:
            Retrieve SMS messages filtered by the date in which they were sent
    
            >>> convert "2022-03-01T15:00:00Z" to a datetime
            >>> use the above as the message date
            >>> read some sms messages whose date sent is the message date
    
        Example 3:
            Retrieve SMS messages that were sent in the specified time period
    
            >>> convert "2022-03-01T15:00:00Z" to a datetime
            >>> use the above as the start date
            >>> convert "2022-03-03T15:00:00Z" to a datetime
            >>> use the above as the end date
            >>> read some sms messages whose date sent is after the start date and whose date sent is before the end date
        """
        client = Client(self._account_sid, self._auth_token)
    
        filter_visitor = SMSMessageFilter()
    
        if filter_expression is not None:
            filter_expression.accept(filter_visitor)
    
        messages = client.messages.list(
            to=filter_visitor.recipient_number,
            from_=filter_visitor.sender_number,
            date_sent=filter_visitor.date_sent,
            date_sent_before=filter_visitor.date_sent_before,
            date_sent_after=filter_visitor.date_sent_after,
        )
    
        if offset is not None:
            messages = messages[offset:]
    
        if limit is not None:
            messages = messages[:limit]
    
        return [SMSMessage.from_message_instance(message) for message in messages]

    5

    BDK Runtime

    A runtime that connects a Book with the Kognitos platform. It is packaged as a Docker image that serves as the foundation for Book containers.

    1

    BDK API

    The core Python library for Book development.

    2

    BDK Book Template

    A Python Cookiecutter template that simplifies the process of developing a new Book.

    3

    BDK Linter

    A linting tool built as a Pylint plugin. It analyzes code throughout development to identify potential issues and ensures Books are implemented, documented, and annotated correctly.

    4

    BDK Poetry Plugin

    A Python dependency management and packaging tool that uses Poetry. It manages dependencies, versioning, formatting, and documentation generation for a Book.

    Planning Checklist
    Planning Your BDK Project
    BDK Setup Guide
    BDK Development Guide
    BDK API Reference
    Cover

    Book that integrates with the OpenWeather API.

    Cover

    Book that integrates with the Twilio API.

    Cover

    Book containing automation procedures that operate on YAML files.

    @procedure

    Overview

    The @procedure decorator is used to denote a method within a Book class as a procedure. This links a method to a specific in the Kognitos platform.

    Syntax

    Concepts

    Understand concepts and how they are utilized in procedures defined by BDK.

    What are Concepts?

    Concepts define how data flows in and out of a procedure:

    • Input concepts represent the inputs that a procedure requires.

    Guidelines

    1. Naming Conventions

    Names must begin with to. This defines the action or intent of the procedure. For example:

    • @procedure("to capitalize a (string)"

    • @procedure("to get the (current temperature)")

    • @procedure("to send an *SMS* message")

    2. Output Concepts

    Output concepts are wrapped in parentheses (). For example:

    3. Proper Nouns

    Proper nouns are wrapped between asterisks **. For example:

    In this example, office365 is considered a proper noun. The procedure is referred to as 'get some users from office365' rather than the office365.

    Parameters

    Parameter
    Type
    Required
    Description

    name

    str

    Yes

    A description that reflects the action or purpose of the procedure. See the syntax for details.

    Keyword Arguments

    Argument
    Type
    Description

    connection_required

    bool

    A boolean that indicates whether a connection to the service is required to execute the procedure. If not specified, it defaults to None.

    noun_phrase

    str

    A string that represents the noun phrase for the procedure.

    Examples

    1. Capitalizing a String

    This method implements a procedure that capitalizes a string with one input concept and one output concept.

    2. Creating an Order in Truckmate

    This method implements a procedure that creates an order in Truckmate. It has one input concept and two output concepts.

    3. Reading SMS messages using the Twilio API

    The following method implements a procedure that reads SMS messages using the Twilio API. In this example, SMS is a proper noun.

    procedure

    Output concepts represent the results that a procedure generates.

    Concept Types

    Input and output concepts can be defined as either standard or custom types.

    Standard Types

    The following Python data types can be used to define concepts:

    Category
    Supported Python Data Types

    Text

    str

    Number

    float, int

    Boolean

    bool

    Bytes

    bytes

    Date

    datetime.datetime, datetime.date, datetime.time

    File

    typing.IO

    Custom Types

    Concepts can also be custom types. Custom concepts must be marked with the @concept decorator. Using a dataclass is recommended. For example:


    Concepts vs. Parameters

    Concepts and parameters are two different entities that are closely related.

    Concepts

    Concepts are operated on by Kognitos procedures. Some concepts are included in the name of the @procedure decorator.

    Parameters

    Parameters are operated on by Python functions. They are defined in a function's signature and are specific to the function's implementation.

    How Are They Related?

    An internal mapping is created between a Python function and a Kognitos procedure. To ensure a correct mapping, concepts and parameters must match.


    Concept-Parameter Matching

    Concepts and parameter names must match to ensure they are properly mapped internally.

    Guidelines

    Match concepts to parameters by following these guidelines:

    1. Replace spaces with underscores.

    2. Drop possessive constructions ('s).

    3. Consider each connectednoun phrase as a separate parameter. Non-essential noun phrases (e.g., "field," "value") included to clarify context do not require a corresponding parameter.

    4. Don't map articles ('an', 'a', 'the') to parameters; only the nouns they connect should be considered.

    5. Define optional parameters in cases where the input concept is not explicitly defined in the procedure name.

    Example 1

    In this example, the concept red car maps to the parameter red_car. The space is replaced with an underscore.

    Example 2

    In the example below:

    • servicenow's ticket maps to ticket

    • field maps to field

    • outlook's standard user maps to standard_user

    Example 3

    In this example, the concept ticket maps to the parameter ticket. The possessive construct ('s) is dropped and the word "field" is ignored.

    Example 4

    In the example below, the concept of priority is not explicitly stated in the procedure name. It maps to the optional function parameter, priority.

    Example 5

    In this example:

    • the city maps to the parameter city

    • the unit maps to the optional parameter the unit

    The output concept, current temperature, is wrapped in parentheses in the procedure name.

    Docstrings

    Understand how to format and write docstrings to ensure your Book is well-documented.

    Format

    BDK docstrings adhere to Google style formatting:

    • Docstrings are placed immediately after function, class, or method definitions.

    • A brief summary that describes the functionality begins the docstring.

    • The summary is followed by organized sections, separated by blank lines and labeled with headers.

    In BDK projects, docstrings are applied to decorated methods and classes.


    Supported Sections

    The following sections are supported in BDK docstrings, with alternative headers available for labeling flexibility.

    1. Arguments and Parameters

    Documents the arguments or parameters that a function or class accepts.

    Supported Headers: Args, Arguments, Parameters, Params

    Example

    2. Return Values

    Documents the return value of a function or method.

    Supported Headers: Returns

    Example

    3. Error Handling

    Lists any exceptions or errors that a function might raise.

    Supported Headers: Raises, Exceptions, Except

    Example

    4. Instance Attributes

    List instance attributes in a class.

    Supported Headers: Attributes

    Example

    5. Code Authors

    Attributes the author(s) of the code.

    Supported Headers: Author

    Example

    6. Labels

    Defines labeled data associated with a function related to .

    Supported Headers: Labels

    Example

    7. OAuth Labels

    Describes OAuth labels for classes decorated with the .

    Supported Headers: OAuth Labels

    Example

    8. OAuth Arguments

    Describes OAuth arguments for classes decorated with the .

    Supported Headers: OAuth Arguments

    Example

    9. Input Concepts

    Describes the for a procedure.

    Supported Headers: Input Concepts

    Example

    10. Output Concepts

    Describes the for a procedure.

    Supported Headers: Output Concepts

    Example

    11. Examples

    Outlines usage examples for a procedure.

    Supported Headers: Example, Examples, Example [1-5]

    Example

    @procedure(name: str, **kwargs)
    @procedure("to capitalize a (string)")
    @procedure("to get some (users) from *office365*")
    @procedure("to capitalize a (string)", connection_required=False)
    def capitalize_string(self, string: str) -> str:
      """
      Capitalizes the input string.
    
      Input Concepts:
        the string: The string value you want to capitalize.
    
      Output Concepts:
        the string: The capitalized string.
    
      Example 1:
        Capitalize the string "hello"
    
        >>> capitalize "hello"
    
      """
      return string.capitalize()
    @procedure("to create an order in truckmate and get the order number and the bill number")
    def create_order(self, order: OrderRequestConcept) -> Tuple[int, str]:
        """
        Create a new order in Truckmate.
    
        Input Concepts:
            the order: The Truckmate order request (acc to openAPI spec)
    
        Output Concepts:
            the order number: Order ID of the created order
            the bill number: Bill Number of the created order
    
        Raises:
            ValueError: If the order is not created successfully.
    
        Example 1:
            Create the order in Truckmate
            >>> create a json
            >>> use the above as the order
            >>> set the order's id to 1234
            >>> set the order's title to "Awesome title"
            >>> set order's body to "This is the body"
            >>> create the order in truckmate and get the order number and bill number
        """
        logger.info("Creating Order in Truckmate")
        post_body = OrdersPostRequest(orders=[order])
        response = post_orders.sync_detailed(
            client=self._client,
            body=post_body,
        )
        if response.status_code == 201:
            orders_response = response.parsed.to_dict()  # type: ignore
            if orders_response.get("orders"):
                order = orders_response.get("orders")[0]  # type: ignore
                return order.get("orderId"), order.get("billNumber")
    
        error_response = response.parsed.to_dict()  # type: ignore
        logger.error(
            "Error while creating order in truckmate:  %s",
            error_response.get("errorText"),
        )
        raise ValueError(error_response)
    @procedure("to read some (*SMS* messages)")
    def read_sms_messages(
        self,
        offset: Optional[int],
        limit: Optional[int],
        filter_expression: Optional[FilterExpression],
    ) -> List[SMSMessage]:
        """
        Read some SMS messages using the Twilio API.
    
        Returns:
            A list of SMS messages that matches the specified filtering criteria
    
        Example 1:
            Retrieve SMS messages filtered by sender and recipient numbers
    
            >>> read some sms messages whose sender number is "+18004445555" and whose recipient number is "+18004446666"
    
        Example 2:
            Retrieve SMS messages filtered by the date in which they were sent
    
            >>> convert "2022-03-01T15:00:00Z" to a datetime
            >>> use the above as the message date
            >>> read some sms messages whose date sent is the message date
    
        Example 3:
            Retrieve SMS messages that were sent in the specified time period
    
            >>> convert "2022-03-01T15:00:00Z" to a datetime
            >>> use the above as the start date
            >>> convert "2022-03-03T15:00:00Z" to a datetime
            >>> use the above as the end date
            >>> read some sms messages whose date sent is after the start date and whose date sent is before the end date
        """
        client = Client(self._account_sid, self._auth_token)
    
        filter_visitor = SMSMessageFilter()
    
        if filter_expression is not None:
            filter_expression.accept(filter_visitor)
    
        messages = client.messages.list(
            to=filter_visitor.recipient_number,
            from_=filter_visitor.sender_number,
            date_sent=filter_visitor.date_sent,
            date_sent_before=filter_visitor.date_sent_before,
            date_sent_after=filter_visitor.date_sent_after,
        )
    
        if offset is not None:
            messages = messages[offset:]
    
        if limit is not None:
            messages = messages[:limit]
    
        return [SMSMessage.from_message_instance(message) for message in messages]
    @concept(is_a="office user")
    @dataclass
    class OfficeUser:
        """
        An Office User represents a user in the Microsoft Graph. It includes key user details such as display name,
        email address, and job title.
    
        Attributes:
            id: The unique identifier for the user.
            display_name: The name displayed in the address book for the user.
            email_address: The user's email address (usually their user principal name).
            job_title: The user's job title.
        """
    
        id: str
        display_name: Optional[str] = None
        email_address: Optional[str] = None
        job_title: Optional[str] = None
        ...
    @procedure("to start a red car")
    def unique_function_name(self, red_car: type) -> output_type:
    @procedure("to send a servicenow's ticket's field to an outlook's standard user)
    def func(self, ticket: Ticket, field: NounPhrase, standard_user: OutlookUser):
    @procedure("to update a ticket's field")
    def update_field(self, ticket: Ticket, new_value: Any):
    @procedure("to assign the task to the user")
    def assign_task(self, task: Task, user: User, priority: Optional[str] = None) -> None:
      """Assign a task to a user.
    
      Input Concepts:
        the task: The action of piece of work that needs to be completed. 
        the user: The user that the action is to be assigned to.
        the priority: The level of importance or urgency of the task.
        
      Example 1:
        Assign the task to the user
    
        >>> assign the task to the user
    
      Example 2:
        Assign the task to the user with a priority
    
        >>> assign the task to the user with
              the priority is "high"
      """
    @procedure("to get the (current temperature) at a city")
    def current_temperature(self, city: NounPhrase, unit: Optional[NounPhrase] = NounPhrase("standard")) -> float:
        """Fetch the current temperature for a specified city.
    
        Input Concepts:
            the city: The name of the city.
            the unit: Unit of measurement.
    
        Output Concepts:
            the current temperature: The current temperature in the specified units of measurement, or None if an error occurs.
    
        Example 1:
            Retrieve the current temperature at London
    
            >>> get the current temperature at London
    
        Example 2:
            Retrieve the current temperature at London in Celsius
    
            >>> get the current temperature at Buenos Aires with
            ...     the unit is metric
        """

    UUID

    uuid.UUID

    Table

    pyarrow.Table, arro3.core.Table, nanoarrow.ArrayStream

    Lists

    List[str], List[int]

    Dictionary

    Dict[str, Any]

    Note:Anycan be any of the other supported types.

    guidelines
    connections
    @oauth decorator
    @oauth decorator
    input concepts
    output concepts
    @procedure("to add two numbers")
    def add_numbers(a: int, b: int = 0) -> int:
        """
        Adds two numbers together.
    
        Args:
            a: The first number.
            b: The second number. Defaults to 0.
        """
        return a + b
    @procedure("to send an *SMS* message")
    def send_sms_message(self, sender_number: str, recipient_number: str, message_body: str) -> Optional[str]:
        """
        Sends an SMS message using the Twilio API.
    
        Returns:
            The SID of the sent message if successful, otherwise None.
        """
    @timeout.setter
    def timeout(self, timeout: float):
        """
        Sets the timeout value in milliseconds.
    
        Args:
            timeout (int): The timeout value to set. Must be a positive integer.
    
        Raises:
            ValueError: If the timeout value is less than or equal to 0.
    
        """
        if timeout <= 0:
            raise ValueError("timeout must be positive")
        self._timeout = timeout
    @concept(is_a="office user")
    @dataclass
    class OfficeUser:
        """
        An Office User represents a user in the Microsoft Graph. It includes key user details such as display name,
        email address, and job title.
    
        Attributes:
            id: The unique identifier for the user.
            display_name: The name displayed in the address book for the user.
            email_address: The user's email address (usually their user principal name).
            job_title: The user's job title.
        """
    
        id: str
        display_name: Optional[str] = None
        email_address: Optional[str] = None
        job_title: Optional[str] = None
    @book(name="Sample Book")
    class SampleBook:
        """
        A book shown as an example.
    
        Author:
          Kognitos
        """
    @connect(noun_phrase="api keys")
    def connect(self, api_key: str):
        """
        Authenticate to Open Weather API using the specified API key.
    
        Labels:
            api_key: API Key
        """
    @oauth(
        id="oauth",
        provider=OAuthProvider.MICROSOFT,
        flows=[OAuthFlow.AUTHORIZATION_CODE, OAuthFlow.CLIENT_CREDENTIALS],
        authorize_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize",
        token_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
        scopes=[
            "https://graph.microsoft.com/Files.ReadWrite",
            "https://graph.microsoft.com/Sites.Manage.All",
            "https://graph.microsoft.com/Sites.ReadWrite.All",
            "https://graph.microsoft.com/User.ReadWrite.All",
            "https://graph.microsoft.com/Directory.ReadWrite.All",
            "https://graph.microsoft.com/Group.ReadWrite.All",
            "https://graph.microsoft.com/GroupMember.ReadWrite.All",
            "https://graph.microsoft.com/Mail.ReadWrite",
            "https://graph.microsoft.com/Mail.ReadWrite.Shared",
            "https://graph.microsoft.com/Mail.Send",
            "https://graph.microsoft.com/Calendars.ReadWrite",
        ],
    )
    @book(name="MicrosoftBook")
    class BaseMicrosoftBook:
        """
        Base class for all Microsoft Books.
    
        OAuth Arguments:
            TENANT_ID: The tenant ID of the Azure AD directory.
    
        OAuth Labels:
            TENANT_ID: Tenant ID
        """
    @oauth(
        id="oauth",
        provider=OAuthProvider.MICROSOFT,
        flows=[OAuthFlow.AUTHORIZATION_CODE, OAuthFlow.CLIENT_CREDENTIALS],
        authorize_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/authorize",
        token_endpoint="https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token",
        scopes=[
            "https://graph.microsoft.com/Files.ReadWrite",
            "https://graph.microsoft.com/Sites.Manage.All",
            "https://graph.microsoft.com/Sites.ReadWrite.All",
            "https://graph.microsoft.com/User.ReadWrite.All",
            "https://graph.microsoft.com/Directory.ReadWrite.All",
            "https://graph.microsoft.com/Group.ReadWrite.All",
            "https://graph.microsoft.com/GroupMember.ReadWrite.All",
            "https://graph.microsoft.com/Mail.ReadWrite",
            "https://graph.microsoft.com/Mail.ReadWrite.Shared",
            "https://graph.microsoft.com/Mail.Send",
            "https://graph.microsoft.com/Calendars.ReadWrite",
        ],
    )
    @book(name="MicrosoftBook")
    class BaseMicrosoftBook:
        """
        Base class for all Microsoft Books.
    
        OAuth Arguments:
            TENANT_ID: The tenant ID of the Azure AD directory.
    
        OAuth Labels:
            TENANT_ID: Tenant ID
        """
    @procedure("to get the (current temperature) at a city")
    def current_temperature(self, city: NounPhrase, unit: Optional[NounPhrase] = NounPhrase("metric")) -> float:
        """
        Fetch the current temperature for a specified city.
    
        Input Concepts:
            the city: The name of the city. Please refer to ISO 3166 for the state codes or country codes.
            the unit: Unit of measurement. standard, metric and imperial units are available. If you do
                not specify the units, metric units will be applied by default.
        """
    @procedure("to get the (current temperature) at a city")
    def current_temperature(self, city: NounPhrase, unit: Optional[NounPhrase] = NounPhrase("metric")) -> float:
        """
        Fetch the current temperature for a specified city.
    
        Output Concepts:
            the current temperature: The current temperature in the specified units of measurement, or None if an error occurs.
        """
    @procedure("to get the (current temperature) at a city")
    def current_temperature(self, city: NounPhrase, unit: Optional[NounPhrase] = NounPhrase("metric")) -> float:
        """
        Fetch the current temperature for a specified city.
    
        Example 1:
            Retrieve the current temperature at London
    
            >>> get the current temperature at London
    
        Example 2:
            Retrieve the current temperature at London in Celsius
    
            >>> get the current temperature at Buenos Aires with
            ...     the unit is metric
        """

    Questions

    Learn how to implement questions in a BDK Book.

    Overview

    In your Book, you may need to ask the user for information. For example, if the Book needs to know when a project should begin, it can ask the user, “What is the project start date?” Or if it needs a username, it can prompt the user with, “What is the username?”

    When this happens, Kognitos surfaces a question to pause the automation and ask the user for information. A question is an exception that signals that the automation cannot move forward without user input. The BDK provides an easy way to add and handle questions in your Book using the ask() method.

    How It Works

    The ask() function in the BDK is used to ask a question. Here’s how it works:

    1

    ask() is Called

    ask() is called inside a procedure to ask a question.

    2

    Usage

    Parameter
    Required
    Description

    To use ask() in your Book, follow these steps:

    1

    Import

    Start by importing the ask() function and Question type from the BDK API:

    2

    Examples

    1. Asking a Question with Different Return Types

    This example shows how to use ask() with a set of mixed-type choices: a string, a datetime, and a float. The return type allows for any of those types, and also accounts for the case where a Question is returned.

    2. Asking Multiple Questions

    Sometimes your Book may need to collect more than one piece of information. You can call ask() multiple times in the same procedure. Here’s an example:

    How This Works

    1. The first call to ask() checks whether the answer for color is present.

      1. If not, it returns a Question.

      2. If answered, it stores the value and restarts the procedure.

    2. Now that the answer for color is present, the procedure checks for the

    Learning a BDK Book

    A guide to learning a custom BDK Book in Kognitos.

    Learning Books

    Before an agent can use a Book’s procedures in a Playground or Process, it must first learn the Book. This allows the agent to recognize the Book and its procedures.

    Follow the steps below to learn your custom Book after deployment.

    How to Learn a BDK Book

    1. Retrieve the HTTPS endpoint from your deployment.

    2. Place this at the beginning of your automation, substituting your own endpoint in the syntax:

    If you implemented any custom , make sure you also to your Book after learning.

    learn "https://<your-endpoint>"
    connections
    connect
    Kognitos Checks for an Answer

    Each time ask() is called, Kognitos checks internally whether it has already stored an answer for that specific question before.

    • If the answer to the question exists, ask() returns the answer value itself.

    • If the answer to the question does not exist, ask() returns a Question.

    3

    Question is Surfaced (if the answer is not present)

    When a Question object is returned from a procedure, Kognitos will:

    1. Forward the question to the user

    2. Pause the automation and wait for a response

    3. Store the answer internally

    4. Restart the procedure from the beginning automatically

      • The second time the procedure runs, ask() will be able to return the answer instead of a Question.

    Note: Kognitos only surfaces a question if it doesn’t have the answer stored internally. By remembering answers, it ensures the same questions aren’t unnecessarily presented to the user.

    Call ask()

    Call the ask() function inside a method decorated with @procedure.

    Provide the concept_name (the unique name of the concept being asked for) and the concept_type (the return type of the answer).

    (Optional) Specifying the Question Text

    You can specify a text to present to the user when the question is prompted in Kognitos. If the text is not specified, questions are presented as Please provide the <concept_name> by default. For example:

    (Optional) Specifying Choices

    You can also guide users to select an answer from a predefined list by passing a choices argument. When choices are set, Kognitos displays the question with a dropdown menu for the user to select from. For example:

    3

    Handle the Result of ask()

    The ask() function can return either the answer or a Question object. To handle both cases, explicitly check the return type:

    • If ask() returns a value, that means the answer already exists internally.

    • If ask() returns a Question, that means the answer does not exist internally. What you do with the Question is up to you — the Book can return it, hold onto it for later, or ignore it entirely. If you want Kognitos to surface the question to the user, return the Question object from the procedure. For example:

    4

    Use the Answer

    Once the ask() function returns a value (and not a Question), you can use the answer just like any other variable in Python — in calculations, conditions, etc. For example:

    5

    Declare Return Types

    Always include return types in your procedure definitions. If your procedure may trigger a Question, the return type must reflect that so the system understands which concept(s) you may be asking for. Make sure to include both the concept name (as a string Literal in nounphrase form) and the concept type in the type hint. For example:

    elephant’s name
    .
    1. If not, it returns a Question.

    2. If answered, it stores the value and restarts the procedure.

  • Once both values are available, the final result is returned.

  • concept_name

    Yes

    The unique name of the concept being asked for.

    concept_type

    Yes

    The return type of the answer.

    text

    No

    (Optional) A message to show the user when the question is prompted in Kognitos.

    choices

    No

    (Optional) A list of predefined options the user can choose from.

    result = ask("project start date", str)
    if isinstance(start_date := ask("project start date", str), Question):
        return start_date
    
    # Use the answer as a datetime object
    date_obj = datetime.strptime(date_str, "%Y-%m-%d")
    project_due_date = date_obj + timedelta(days=30)
    @procedure("to get a project due (date)")
    def get_due_date(self) -> str | Question[Literal["project start date"], str]:
    ask(concept_name: NounPhrasesStringLiteral, concept_type: Type[T], text: Optional[str] = None, choices: Optional[List[T]] = None) -> Union[T, Question[NounPhrasesStringLiteral, T]]
    from kognitos.bdk.api.questions import ask, Question
    @procedure("to get a (value)")
    def get_value(self) -> str | datetime | float | Question[Literal["value"], str | datetime | float]:
    
        choices = ["Hello!", datetime(2023, 1, 1, tzinfo=timezone.utc), 123.456]
        if isinstance(answer := ask("value", Union[str, datetime, float], choices=choices), Question):
            return answer
        return answer
    from typing import Literal
    from kognitos import procedure
    from kognitos.bdk.api.questions import ask, Question
    
    @procedure("to get a (text)")
    def get_text(self) -> str | Question[Literal["color"], str] | Question[Literal["elephant's name"], str]:
        """
        Get a text
        """
        # First: Ask the user to pick a color
        if isinstance(answer := ask("color", str, text="Pick a color", choices=["red", "blue", "green"]), Question):
            return answer
        color = answer
    
        # Second: Ask for the elephant's name
        if isinstance(answer := ask("elephant's name", str, text="What is the elephant's name?"), Question):
            return answer
        name = answer
    
        # You can now use both answers
        return f"The elephant named {name} is painted {color}."
    result = ask("project start date", str, text="What is the start date of the project?")
    result = ask("project start date", str, choices=["2025-07-22", "2025-07-25", "2025-07-28"])
    if isinstance(result := ask("project start date", str), Question)
        return result
    procedure

    Development Guide

    Learn how to develop, package, and run your Book locally with the BDK.

    Have You Completed the Setup?

    Ensure you’ve followed all the steps to set up your BDK project before proceeding. This includes configuring your environment and installing dependencies.

    Project Structure

    The project is organized with the following structure:

    Root Directory

    The project root directory is named using the Project Slug (default: sample_book).

    Source Code Directory

    • 📃 book.py: Contains the core class where your Book is defined and implemented.

    • 📁 data: Holds images like 🖼️ icon.svg, the default SVG used as a Book's icon.

    Test Directory

    • 📃 test_book.py: Contains unit tests to validate the Book's functionality.

    Configuration and Dependency Files

    • 📃 Dockerfile: Builds the Docker image.

    • 📃 pyproject.toml: Manages dependencies and settings.

    • 📃 poetry_scripts.py: Custom automation script.

    Documentation

    • 📃 README.md: Project overview documentation, including setup and usage instructions.

    • 📃 USAGE.md: Reference documentation for your Book. This file is not included by default. For details on how to generate it, see .


    Implementation

    Implement your Book in book.py. This class is auto-generated by the BDK Template and serves as the starting point for your Book’s implementation. Use decorators to configure settings, define procedures, and establish API connections. Refer to the BDK API Reference and for details.


    Managing Dependencies

    is used for Python dependency management in BDK projects. To add an external Python dependency to your project, refer to the command, which adds packages to your pyproject.toml and installs them.


    Development Commands

    This section outlines essential commands for Book development and testing.

    Formatting

    Code formatting automatically adjusts your code's structure, ensuring adherence to coding standards, readability, and maintainability. To apply the formatting, run:

    Linting

    is used as the source linter for BDK projects. The linter analyzes and enforce coding standards, checks for errors in Python code, and improves code quality. To run the linter:

    Generating Documentation

    This command will generate a comprehensive USAGE.md file by extracting and formatting the from your code. To generate the documentation, run:

    Testing

    is used to test and validate your book. To run the test suite:


    Packaging with Docker

    Build a Docker image to package your Book:

    Architecture Compatibility

    This image pulls the BDK Runtime base image from Docker Hub, which is currently available only for the linux/amd64 architecture. If you're using a machine with a different architecture, you can use the --platform flag to emulate linux/amd64 when building the image:


    Running Your Book Locally

    To test your Book in Kognitos before deployment, you can run your Docker image locally with . Ngrok is a tool that creates a secure tunnel to your local machine and provides a public URL to make your local Docker image accessible from the platform.

    1. Install ngrok

    Follow the installation steps for based on your system. You will need to sign up for a free account.

    2. Obtain your ngrok authtoken

    Navigate to Your Authtoken in the ngrok portal and copy your token.

    3. Add your authtoken

    4. Configure the ngrok api key as an environment variable:

    5. Build & run in ngrok mode

    This command will invoke a custom script that builds the Docker image and runs it in ngrok mode:

    If you are running into issues with `poetry run host`, you can and run your book manually with the following command:

    Make sure to replace auth_token, project_slug, and version with your own values.

    6. Copy the URL

    You'll see the ngrok address in the following format in the logs. Copy the URL:

    7. Add the URL to your automation

    Copy the URL and paste it into your Kognitos Playground using this syntax:

    If you implemented any custom , make sure you also to your Book after learning.

    📃 poetry.lock: Locks dependency versions.

    Generating Documentation
    example Books
    Poetry
    poetry add
    Pylint
    docstrings
    Pytest
    ngrok
    ngrok
    package
    connections
    connect
    📂 sample_book
    ├── 📁 src
    │   └── 📁 sample_book
    │       ├── 📃 __init__.py
    │       ├── 📃 __version__.py
    │       └── 📃 book.py
    │       └── 📁 data
    │           └── 🖼️ icon.svg
    ├── 📁 tests
    │   └── 📃 test_book.py
    ├── 📃 Dockerfile
    ├── 📃 pyproject.toml
    ├── 📃 poetry_scripts.py
    ├── 📃 poetry.lock
    ├── 📃 README.md
    └── 📃 USAGE.md
    poetry run format
    poetry run lint
    poetry run doc
    poetry run tests
    docker build -t <project_slug>:<version> .
    docker build --platform linux/amd64 -t <project_slug>:<version> .
    ngrok config add-authtoken <token>
    export NGROK_AUTHTOKEN=<token>
    poetry run host
    docker run -e BDK_SERVER_MODE=ngrok -e NGROK_AUTHTOKEN=<auth_token> <project_slug>:<version>
    listening on https://<SOME_UUID>.ngrok-free.app
    learn "https://f1d7-100-34-252-18.ngrok-free.app"
    Books
    library