Skip to content

API Reference - Main Package#

Init file for Intentional's sample tools.

GetCurrentDateTimeTool #

Bases: Tool

Simple tool to get the current date and time.

Source code in intentional/sample_tools.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class GetCurrentDateTimeTool(Tool):
    """
    Simple tool to get the current date and time.
    """

    id = "get_current_date_and_time"
    name = "get_current_date_and_time"
    description = "Get the current date and time in the format 'YYYY-MM-DD HH:MM:SS'."
    parameters = []

    async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
        """
        Returns the current time.
        """
        current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log.debug("Getting the current date and time.", current_date_time=current_datetime)
        return current_datetime

run(params=None) async #

Returns the current time.

Source code in intentional/sample_tools.py
55
56
57
58
59
60
61
async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
    """
    Returns the current time.
    """
    current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log.debug("Getting the current date and time.", current_date_time=current_datetime)
    return current_datetime

RescheduleInterviewTool #

Bases: Tool

Mock tool to reschedule an interview.

Source code in intentional/sample_tools.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class RescheduleInterviewTool(Tool):
    """
    Mock tool to reschedule an interview.
    """

    id = "reschedule_interview"
    name = "reschedule_interview"
    description = "Set a new date and time for the interview in the database."
    parameters = [
        ToolParameter(
            "date",
            "The new date for the interview.",
            "string",
            True,
            None,
        ),
        ToolParameter(
            "time",
            "The new time for the interview.",
            "string",
            True,
            None,
        ),
    ]

    async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
        """
        Returns the current time.
        """
        log.debug("Rescheduling the interview.")
        return "The interview was rescheduled successfully."

run(params=None) async #

Returns the current time.

Source code in intentional/sample_tools.py
89
90
91
92
93
94
async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
    """
    Returns the current time.
    """
    log.debug("Rescheduling the interview.")
    return "The interview was rescheduled successfully."

__about__ #

Package descriptors for intentional.

cli #

Entry point for the Intentional CLI.

draw_intent_graph_from_config(path) async #

Load the intent router from the configuration file.

Source code in intentional/cli.py
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
async def draw_intent_graph_from_config(path: str) -> IntentRouter:
    """
    Load the intent router from the configuration file.
    """
    log.debug("Loading YAML configuration file", config_file_path=path)

    with open(path, "r", encoding="utf-8") as file:
        config = yaml.safe_load(file)
    log.debug("Loading bot interface", bot_interface_config=json.dumps(config, indent=4))

    plugins = config.pop("plugins")
    log.debug("Collected_plugins", plugins=plugins)
    for plugin in plugins:
        import_plugin(plugin)

    # Remove YAML extension from path
    path = path.rsplit(".", 1)[0]
    intent_router = IntentRouter(config.pop("conversation", {}))
    return await to_image(intent_router, path + ".png")

main() #

Entry point for the Intentional CLI.

Source code in intentional/cli.py
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
def main():
    """
    Entry point for the Intentional CLI.
    """
    parser = argparse.ArgumentParser()
    parser.add_argument("path", help="the path to the configuration file to load.", type=str)
    parser.add_argument("--draw", help="Draw the graph.", action="store_true")
    parser.add_argument(
        "--log-cli-level",
        help="Select the logging level to the console.",
        type=str,
        default="INFO",
    )
    parser.add_argument("--log-file", help="Path to the log file.", type=str)
    parser.add_argument(
        "--log-file-level",
        help="Select the logging level to the file. Ignore if no path is specified with --log-file",
        type=str,
        default="DEBUG",
    )
    args = parser.parse_args()

    # Set the CLI log level
    cli_level = logging.getLevelName(args.log_cli_level.upper())
    structlog.configure(wrapper_class=structlog.make_filtering_bound_logger(cli_level))

    if args.log_file:
        # https://www.structlog.org/en/stable/standard-library.html
        file_level = logging.getLevelName(args.log_file_level.upper())
        timestamper = structlog.processors.TimeStamper(fmt="iso")
        pre_chain = [
            structlog.stdlib.add_log_level,
            structlog.stdlib.ExtraAdder(),
            timestamper,
        ]
        logging.config.dictConfig(
            {
                "version": 1,
                "disable_existing_loggers": False,
                "handlers": {
                    "default": {
                        "level": cli_level,
                        "class": "logging.StreamHandler",
                        "formatter": "colored",
                    },
                    "file": {
                        "level": file_level,
                        "class": "logging.handlers.WatchedFileHandler",
                        "filename": args.log_file,
                        "formatter": "json",
                    },
                },
                "formatters": {
                    "json": {
                        "()": "pythonjsonlogger.jsonlogger.JsonFormatter",
                        "fmt": "%(asctime)s %(levelname)s %(message)s",
                    },
                    "colored": {
                        "()": structlog.stdlib.ProcessorFormatter,
                        "processors": [
                            structlog.stdlib.ProcessorFormatter.remove_processors_meta,
                            structlog.dev.ConsoleRenderer(colors=True),
                        ],
                        "foreign_pre_chain": pre_chain,
                    },
                },
                "loggers": {
                    "": {
                        "handlers": ["default", "file"],
                        "level": "DEBUG",
                        "propagate": True,
                    },
                },
            }
        )
        structlog.configure(
            processors=[
                structlog.stdlib.add_log_level,
                structlog.stdlib.PositionalArgumentsFormatter(),
                timestamper,
                structlog.processors.StackInfoRenderer(),
                structlog.processors.format_exc_info,
                structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
            ],
            logger_factory=structlog.stdlib.LoggerFactory(),
            wrapper_class=structlog.stdlib.BoundLogger,
            cache_logger_on_first_use=True,
        )

    if args.draw:
        asyncio.run(draw_intent_graph_from_config(args.path))
        return

    bot = load_configuration_file(args.path)
    asyncio.run(bot.run())

draw #

Helpers that allow the user to draw the bot's graph.

to_bytes(intent_router, mermaid_domain='https://mermaid.ink/img/') async #

Uses mermaid.ink to render the intent's graph into an image.

Parameters:

Name Type Description Default
intent_router IntentRouter

the intents graph to draw.

required
mermaid_domain str

the domain of your Mermaid instance, if you have your own. Defaults to the public mermaid.ink domain.

'https://mermaid.ink/img/'

Returns:

Type Description
bytes

The bytes of the resulting image. To save them into an image file, do:

bytes

```python

bytes

image = to_image(intent_router)

bytes

with open(image_path, "wb") as imagefile: imagefile.write(image)

bytes

```

Source code in intentional/draw.py
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
async def to_bytes(intent_router: IntentRouter, mermaid_domain: str = "https://mermaid.ink/img/") -> bytes:
    """
    Uses mermaid.ink to render the intent's graph into an image.

    Args:
        intent_router: the intents graph to draw.
        mermaid_domain: the domain of your Mermaid instance, if you have your own.
            Defaults to the public mermaid.ink domain.

    Returns:
        The bytes of the resulting image. To save them into an image file, do:

        ```python
        image = to_image(intent_router)
        with open(image_path, "wb") as imagefile:
            imagefile.write(image)
        ```
    """
    url = to_mermaid_link(intent_router, mermaid_domain)
    resp = requests.get(url, timeout=10)
    if resp.status_code >= 400:
        resp.raise_for_status()
    return resp.content

to_image(intent_router, image_path) async #

Saves an image of the intent's graph at the given path.

Parameters:

Name Type Description Default
intent_router IntentRouter

the intents graph to draw.

required
image_path Path

where to save the resulting image

required
Source code in intentional/draw.py
27
28
29
30
31
32
33
34
35
36
37
async def to_image(intent_router: IntentRouter, image_path: Path) -> None:
    """
    Saves an image of the intent's graph at the given path.

    Args:
        intent_router: the intents graph to draw.
        image_path: where to save the resulting image
    """
    image = await to_bytes(intent_router)
    with open(image_path, "wb") as imagefile:
        imagefile.write(image)

to_mermaid_diagram(intent_router) #

Creates a textual representation of the intents graph in a way that Mermaid.ink can render.

Keep in mind that this function should be able to render also malformed graphs as far as possible, because it can be used as a debugging tool to visualize bad bot configurations within error messages.

Parameters:

Name Type Description Default
intent_router IntentRouter

the intents graph to draw.

required

Returns:

Type Description
str

A string containing the description of the graph.

Source code in intentional/draw.py
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
def to_mermaid_diagram(intent_router: IntentRouter) -> str:
    """
    Creates a textual representation of the intents graph in a way that Mermaid.ink can render.

    Keep in mind that this function should be able to render also malformed graphs as
    far as possible, because it can be used as a debugging tool to visualize bad bot
    configurations within error messages.

    Args:
        intent_router: the intents graph to draw.

    Returns:
        A string containing the description of the graph.
    """
    stages = {node: f'{node}["<b>{node}</b>"]' for node in intent_router.stages}
    unique_counter = 0
    connections_list = []

    processed_nodes = set()
    for origin, target, key in intent_router.graph.edges:
        # 'end' is reserved in Mermaid
        if target == "_end_":
            unique_counter += 1
            edge_string = f'{stages[origin]} -- {key} --> END{unique_counter}["<b>end</b>"]:::highlight'
        elif target == "_backtrack_":
            unique_counter += 1
            edge_string = f'{stages[origin]} -- {key} --> BACKTRACK{unique_counter}("<b>backtrack</b>"):::highlight'

        else:
            edge_string = f"{stages[origin]} -- {key} --> {stages.get(target, target)}"
        connections_list.append(edge_string)

        # Indirect connections
        if origin not in processed_nodes:
            processed_nodes.add(origin)

            if intent_router.stages[origin].accessible_from == ["_start_"]:
                edge_string = f'START("<b>start</b>"):::highlight ---> {stages[origin]}'
            elif intent_router.stages[origin].accessible_from == ["_all_"]:
                unique_counter += 1
                edge_string = f'ALL{unique_counter}("<b>all</b>"):::highlight ---> {stages[origin]}'
            elif intent_router.stages[origin].accessible_from:
                unique_counter += 1
                accessible_from_str = ",<br>".join(intent_router.stages[origin].accessible_from)
                edge_string = f'FROM{unique_counter}("<b>{accessible_from_str}</b>"):::highlight ---> {stages[origin]}'
            else:
                continue
            connections_list.append(edge_string)

    connections = "\n".join(connections_list)
    graph_styled = MERMAID_STYLED_TEMPLATE.format(connections=connections)
    log.debug("Mermaid graph created", mermaid_graph=graph_styled)
    return graph_styled

Generated a URL that contains a rendering of the graph of the intents into an image.

Parameters:

Name Type Description Default
intent_router IntentRouter

the intents graph to draw.

required
mermaid_domain str

the domain of your Mermaid instance, if you have your own. Defaults to the public mermaid.ink domain.

'https://mermaid.ink/img/'

Returns:

Type Description
str

A URL on Mermaid.ink with the graph of the intents.

Source code in intentional/draw.py
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
def to_mermaid_link(intent_router: IntentRouter, mermaid_domain: str = "https://mermaid.ink/img/") -> str:
    """
    Generated a URL that contains a rendering of the graph of the intents into an image.

    Args:
        intent_router: the intents graph to draw.
        mermaid_domain: the domain of your Mermaid instance, if you have your own.
            Defaults to the public mermaid.ink domain.

    Returns:
        A URL on Mermaid.ink with the graph of the intents.
    """
    graph_styled = to_mermaid_diagram(intent_router)
    graphbytes = graph_styled.encode("ascii")
    base64_bytes = base64.b64encode(graphbytes)
    base64_string = base64_bytes.decode("ascii")
    return mermaid_domain + base64_string

sample_tools #

Sample tools for Intentional's examples.

GetCurrentDateTimeTool #

Bases: Tool

Simple tool to get the current date and time.

Source code in intentional/sample_tools.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class GetCurrentDateTimeTool(Tool):
    """
    Simple tool to get the current date and time.
    """

    id = "get_current_date_and_time"
    name = "get_current_date_and_time"
    description = "Get the current date and time in the format 'YYYY-MM-DD HH:MM:SS'."
    parameters = []

    async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
        """
        Returns the current time.
        """
        current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
        log.debug("Getting the current date and time.", current_date_time=current_datetime)
        return current_datetime

run(params=None) async #

Returns the current time.

Source code in intentional/sample_tools.py
55
56
57
58
59
60
61
async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
    """
    Returns the current time.
    """
    current_datetime = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log.debug("Getting the current date and time.", current_date_time=current_datetime)
    return current_datetime

MockTool #

Bases: Tool

Simple tool that returns a fixed response to a fixed parameter value.

Accepts a single parameter, "request", which is a string.

Source code in intentional/sample_tools.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class MockTool(Tool):
    """
    Simple tool that returns a fixed response to a fixed parameter value.

    Accepts a single parameter, "request", which is a string.
    """

    id = "mock_tool"

    def __init__(
        self, name, description, input_description, responses_dictionary=None, default_response=None
    ):  # pylint: disable=too-many-arguments,too-many-positional-arguments
        self.name = name
        self.description = description
        self.parameters = [ToolParameter("request", input_description, "string", True, None)]
        self.responses_dictionary = responses_dictionary or {}
        self.default_response = default_response

    async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
        """
        Returns a fixed response to a fixed parameter value.
        """
        response = self.responses_dictionary.get(params["request"], self.default_response)
        if response:
            log.debug("ExampleTool found a match", request=params["request"], response=response)
        else:
            log.debug("ExampleTool did not find a match", request=params["request"])
        return response

run(params=None) async #

Returns a fixed response to a fixed parameter value.

Source code in intentional/sample_tools.py
33
34
35
36
37
38
39
40
41
42
async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
    """
    Returns a fixed response to a fixed parameter value.
    """
    response = self.responses_dictionary.get(params["request"], self.default_response)
    if response:
        log.debug("ExampleTool found a match", request=params["request"], response=response)
    else:
        log.debug("ExampleTool did not find a match", request=params["request"])
    return response

RescheduleInterviewTool #

Bases: Tool

Mock tool to reschedule an interview.

Source code in intentional/sample_tools.py
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class RescheduleInterviewTool(Tool):
    """
    Mock tool to reschedule an interview.
    """

    id = "reschedule_interview"
    name = "reschedule_interview"
    description = "Set a new date and time for the interview in the database."
    parameters = [
        ToolParameter(
            "date",
            "The new date for the interview.",
            "string",
            True,
            None,
        ),
        ToolParameter(
            "time",
            "The new time for the interview.",
            "string",
            True,
            None,
        ),
    ]

    async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
        """
        Returns the current time.
        """
        log.debug("Rescheduling the interview.")
        return "The interview was rescheduled successfully."

run(params=None) async #

Returns the current time.

Source code in intentional/sample_tools.py
89
90
91
92
93
94
async def run(self, params: Optional[Dict[str, Any]] = None) -> str:
    """
    Returns the current time.
    """
    log.debug("Rescheduling the interview.")
    return "The interview was rescheduled successfully."