Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add context guide #244

Merged
merged 16 commits into from
Oct 16, 2023
Merged
97 changes: 54 additions & 43 deletions dff/script/core/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

def get_last_index(dictionary: dict) -> int:
"""
Obtaining the last index from the `dictionary`. Functions returns `-1` if the `dict` is empty.
Obtain the last index from the `dictionary`. Return `-1` if the `dict` is empty.

:param dictionary: Dictionary with unsorted keys.
:return: Last index from the `dictionary`.
Expand All @@ -44,6 +44,9 @@ def get_last_index(dictionary: dict) -> int:
class Context(BaseModel):
"""
A structure that is used to store data about the context of a dialog.

Avoid storing unserializable data in the fields of this class in order for
context storages to work.
"""

id: Union[UUID, int, str] = Field(default_factory=uuid4)
Expand Down Expand Up @@ -77,26 +80,28 @@ class Context(BaseModel):
`misc` stores any custom data. The scripting doesn't use this dictionary by default,
so storage of any data won't reflect on the work on the internal Dialog Flow Scripting functions.

Avoid storing unserializable data in order for context storages to work.

- key - Arbitrary data name.
- value - Arbitrary data.
"""
validation: bool = False
"""
`validation` is a flag that signals that :py:class:`~dff.script.Pipeline`,
while being initialized, checks the :py:class:`~dff.script.Script`.
`validation` is a flag that signals that :py:class:`~dff.pipeline.pipeline.pipeline.Pipeline`,
while being initialized, checks the :py:class:`~dff.script.core.script.Script`.
The functions that can give not valid data
while being validated must use this flag to take the validation mode into account.
Otherwise the validation will not be passed.
"""
framework_states: Dict[ModuleName, Dict[str, Any]] = {}
"""
`framework_states` is used for addons states or for
:py:class:`~dff.script.Pipeline`'s states.
:py:class:`~dff.script.Pipeline`
:py:class:`~dff.pipeline.pipeline.pipeline.Pipeline`'s states.
:py:class:`~dff.pipeline.pipeline.pipeline.Pipeline`
records all its intermediate conditions into the `framework_states`.
After :py:class:`~dff.script.Context` processing is finished,
:py:class:`~dff.script.Pipeline` resets `framework_states` and
returns :py:class:`~dff.script.Context`.
After :py:class:`~.Context` processing is finished,
:py:class:`~dff.pipeline.pipeline.pipeline.Pipeline` resets `framework_states` and
returns :py:class:`~.Context`.

- key - Temporary variable name.
- value - Temporary variable data.
Expand All @@ -106,7 +111,7 @@ class Context(BaseModel):
@classmethod
def sort_dict_keys(cls, dictionary: dict) -> dict:
"""
Sorting the keys in the `dictionary`. This needs to be done after deserialization,
Sort the keys in the `dictionary`. This needs to be done after deserialization,
since the keys are deserialized in a random order.

:param dictionary: Dictionary with unsorted keys.
Expand All @@ -117,16 +122,15 @@ def sort_dict_keys(cls, dictionary: dict) -> dict:
@classmethod
def cast(cls, ctx: Optional[Union["Context", dict, str]] = None, *args, **kwargs) -> "Context":
"""
Transforms different data types to the objects of
:py:class:`~dff.script.Context` class.
Returns an object of :py:class:`~dff.script.Context`
Transform different data types to the objects of the
:py:class:`~.Context` class.
Return an object of the :py:class:`~.Context`
type that is initialized by the input data.

:param ctx: Different data types, that are used to initialize object of
:py:class:`~dff.script.Context` type.
The empty object of :py:class:`~dff.script.Context`
type is created if no data are given.
:return: Object of :py:class:`~dff.script.Context`
:param ctx: Data that is used to initialize an object of the
:py:class:`~.Context` type.
An empty :py:class:`~.Context` object is returned if no data is given.
:return: Object of the :py:class:`~.Context`
type that is initialized by the input data.
"""
if not ctx:
Expand All @@ -137,14 +141,15 @@ def cast(cls, ctx: Optional[Union["Context", dict, str]] = None, *args, **kwargs
ctx = Context.model_validate_json(ctx)
elif not issubclass(type(ctx), Context):
raise ValueError(
f"context expected as sub class of Context class or object of dict/str(json) type, but got {ctx}"
f"Context expected to be an instance of the Context class "
f"or an instance of the dict/str(json) type. Got: {type(ctx)}"
)
return ctx

def add_request(self, request: Message):
"""
Adds to the context the next `request` corresponding to the next turn.
The addition takes place in the `requests` and `new_index = last_index + 1`.
Add a new `request` to the context.
The new `request` is added with the index of `last_index + 1`.

:param request: `request` to be added to the context.
"""
Expand All @@ -154,8 +159,8 @@ def add_request(self, request: Message):

def add_response(self, response: Message):
"""
Adds to the context the next `response` corresponding to the next turn.
The addition takes place in the `responses`, and `new_index = last_index + 1`.
Add a new `response` to the context.
The new `response` is added with the index of `last_index + 1`.

:param response: `response` to be added to the context.
"""
Expand All @@ -165,9 +170,8 @@ def add_response(self, response: Message):

def add_label(self, label: NodeLabel2Type):
"""
Adds to the context the next :py:const:`label <dff.script.NodeLabel2Type>`,
corresponding to the next turn.
The addition takes place in the `labels`, and `new_index = last_index + 1`.
Add a new :py:data:`~.NodeLabel2Type` to the context.
The new `label` is added with the index of `last_index + 1`.

:param label: `label` that we need to add to the context.
"""
Expand All @@ -180,12 +184,12 @@ def clear(
field_names: Union[Set[str], List[str]] = {"requests", "responses", "labels"},
):
"""
Deletes all recordings from the `requests`/`responses`/`labels` except for
Delete all records from the `requests`/`responses`/`labels` except for
the last `hold_last_n_indices` turns.
If `field_names` contains `misc` field, `misc` field is fully cleared.

:param hold_last_n_indices: Number of last turns that remain under clearing.
:param field_names: Properties of :py:class:`~dff.script.Context` we need to clear.
:param hold_last_n_indices: Number of last turns to keep.
:param field_names: Properties of :py:class:`~.Context` to clear.
Defaults to {"requests", "responses", "labels"}
"""
field_names = field_names if isinstance(field_names, set) else set(field_names)
Expand All @@ -206,26 +210,29 @@ def clear(
@property
def last_label(self) -> Optional[NodeLabel2Type]:
"""
Returns the last :py:const:`~dff.script.NodeLabel2Type` of
the :py:class:`~dff.script.Context`.
Returns `None` if `labels` is empty.
Return the last :py:data:`~.NodeLabel2Type` of
the :py:class:`~.Context`.
Return `None` if `labels` is empty.

Since `start_label` is not added to the `labels` field,
empty `labels` usually indicates that the current node is the `start_node`.
"""
last_index = get_last_index(self.labels)
return self.labels.get(last_index)

@property
def last_response(self) -> Optional[Message]:
"""
Returns the last `response` of the current :py:class:`~dff.script.Context`.
Returns `None` if `responses` is empty.
Return the last `response` of the current :py:class:`~.Context`.
Return `None` if `responses` is empty.
"""
last_index = get_last_index(self.responses)
return self.responses.get(last_index)

@last_response.setter
def last_response(self, response: Optional[Message]):
"""
Sets the last `response` of the current :py:class:`~dff.core.engine.core.context.Context`.
Set the last `response` of the current :py:class:`~.Context`.
Required for use with various response wrappers.
"""
last_index = get_last_index(self.responses)
Expand All @@ -234,16 +241,16 @@ def last_response(self, response: Optional[Message]):
@property
def last_request(self) -> Optional[Message]:
"""
Returns the last `request` of the current :py:class:`~dff.script.Context`.
Returns `None` if `requests` is empty.
Return the last `request` of the current :py:class:`~.Context`.
Return `None` if `requests` is empty.
"""
last_index = get_last_index(self.requests)
return self.requests.get(last_index)

@last_request.setter
def last_request(self, request: Optional[Message]):
"""
Sets the last `request` of the current :py:class:`~dff.core.engine.core.context.Context`.
Set the last `request` of the current :py:class:`~.Context`.
Required for use with various request wrappers.
"""
last_index = get_last_index(self.requests)
Expand All @@ -252,7 +259,7 @@ def last_request(self, request: Optional[Message]):
@property
def current_node(self) -> Optional[Node]:
"""
Returns current :py:class:`~dff.script.Node`.
Return current :py:class:`~dff.script.core.script.Node`.
"""
actor = self.framework_states.get("actor", {})
node = (
Expand All @@ -264,25 +271,29 @@ def current_node(self) -> Optional[Node]:
)
if node is None:
logger.warning(
"The `current_node` exists when an actor is running between `ActorStage.GET_PREVIOUS_NODE`"
" and `ActorStage.FINISH_TURN`"
"The `current_node` method should be called "
"when an actor is running between the "
"`ActorStage.GET_PREVIOUS_NODE` and `ActorStage.FINISH_TURN` stages."
)

return node

def overwrite_current_node_in_processing(self, processed_node: Node):
"""
Overwrites the current node with a processed node. This method only works in processing functions.
Set the current node to be `processed_node`.
This method only works in processing functions (pre-response and pre-transition).

The actual current node is not changed.

:param processed_node: `node` that we need to overwrite current node.
:param processed_node: `node` to set as the current node.
"""
is_processing = self.framework_states.get("actor", {}).get("processed_node")
if is_processing:
self.framework_states["actor"]["processed_node"] = Node.model_validate(processed_node)
else:
logger.warning(
f"The `{self.overwrite_current_node_in_processing.__name__}` "
"function can only be run during processing functions."
"method can only be called from processing functions (either pre-response or pre-transition)."
)


Expand Down
7 changes: 7 additions & 0 deletions docs/source/user_guides.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ those include but are not limited to: dialog graph creation, specifying start an
setting transitions and conditions, using ``Context`` object in order to receive information
about current script execution.

:doc:`Context guide <./user_guides/context_guide>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``context guide`` walks you through the details of working with the
``Context`` object, the backbone of the DFF API, including most of the relevant fields and methods.

:doc:`Superset guide <./user_guides/superset_guide>`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Expand All @@ -22,4 +28,5 @@ Superset dashboard shipped with DFF.
:hidden:

user_guides/basic_conceptions
user_guides/context_guide
user_guides/superset_guide
8 changes: 4 additions & 4 deletions docs/source/user_guides/basic_conceptions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ That's what we've changed:

.. note::

See `documentation of Context object`_.
See `guide on Context objects`_.

* Transitions were changed: transitions to next, previous and current node were replaced with special
standard transitions.
Expand All @@ -268,7 +268,7 @@ For example:

* You can serialize context (available on every transition and response)
to json or dictionary in order to debug it or extract some values.
See `tutorial on context serialization`_.
See `guide on context serialization`_.

* You can alter user input and modify generated responses.
User input can be altered with ``PRE_RESPONSE_PROCESSING`` and will happen **before** response generation.
Expand All @@ -293,11 +293,11 @@ Happy building!

.. _tutorial on basic dialog structure: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.1_basics.html
.. _tutorial on response functions: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.3_responses.html
.. _documentation of Context object: https://deeppavlov.github.io/dialog_flow_framework/apiref/dff.script.core.context.html
.. _guide on Context objects: ../user_guides/context_guide.html
.. _tutorial on transitions: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.4_transitions.html
.. _tutorial on conditions: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.2_conditions.html
.. _tutorial on global transitions: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.5_global_transitions.html
.. _tutorial on context serialization: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.6_context_serialization.html
.. _guide on context serialization: ../user_guides/context_guide.html#serialization
.. _tutorial on pre-response processing: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.7_pre_response_processing.html
.. _tutorial on pre-transition processing: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.9_pre_transitions_processing.html
.. _tutorial on script MISC: https://deeppavlov.github.io/dialog_flow_framework/tutorials/tutorials.script.core.8_misc.html
Loading
Loading