# Questions

## 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 toolkit provides an easy way to add and handle questions in your Book using the **`ask()`** method.

## How It Works

The `ask()` function is used to **ask a question**. Here’s how it works:

{% stepper %}
{% step %}
**ask() is Called**

`ask()` is called inside a procedure to ask a question.
{% endstep %}

{% step %}
**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`.
  {% endstep %}

{% step %}
**Question is Surfaced&#x20;*****(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.*

{% hint style="info" %}
**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.
{% endhint %}
{% endstep %}
{% endstepper %}

## Usage

```python
ask(concept_name: NounPhrasesStringLiteral, concept_type: Type[T], text: Optional[str] = None, choices: Optional[List[T]] = None) -> Union[T, Question[NounPhrasesStringLiteral, T]]
```

| Parameter      | Required | Description                                                                        |
| -------------- | -------- | ---------------------------------------------------------------------------------- |
| `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.                |

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

{% stepper %}
{% step %}
**Import**

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

```python
from kognitos.bdk.api.questions import ask, Question
```

{% endstep %}

{% step %}
**Call ask()**

Call the `ask()` function inside a method decorated with [`@procedure`](https://docs.kognitos.com/legacy/legacy-experience/books/custom-books/api-reference/decorators/procedure-decorator).

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

```python
result = ask("project start date", str)
```

***(Optional)*****&#x20;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:

```python
result = ask("project start date", str, text="What is the start date of the project?")
```

***(Optional)*****&#x20;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:

```python
result = ask("project start date", str, choices=["2025-07-22", "2025-07-25", "2025-07-28"])
```

{% endstep %}

{% step %}
**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:

```python
if isinstance(result := ask("project start date", str), Question)
    return result
```

{% endstep %}

{% step %}
**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:

```python
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)
```

{% endstep %}

{% step %}
**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](https://docs.kognitos.com/legacy/legacy-experience/books/custom-books/api-reference/noun-phrases) form) and the **concept type** in the type hint. For example:

```python
@procedure("to get a project due (date)")
def get_due_date(self) -> str | Question[Literal["project start date"], str]:
```

{% endstep %}
{% endstepper %}

## 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.

```python
@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
```

### 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:

```python
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}."
```

#### 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 **elephant’s name**.
   1. If not, it returns a Question.
   2. If answered, it stores the value and restarts the procedure.
3. Once both values are available, the final result is returned.
