Skip to main content

Async behavior tree

Project description

Async Behaviour Tree for Python

PyPI Version PyPI License

Versions following Semantic Versioning

Requires Python 3.11+. For Python 3.9/3.10 support use 1.x releases.

See documentation.

Overview

What's a behavior tree ?

Unlike a Finite State Machine, a Behaviour Tree is a tree of hierarchical nodes that controls the flow of decision and the execution of "tasks" or, as we will call them further, "Actions". -- behaviortree

If you're new (or not) to behavior tree, you could spend some time on this few links:

Few implementation libraries:

  • task_behavior_engine A behavior tree based task engine written in Python
  • pi_trees a Python/ROS library for implementing Behavior Trees
  • pr_behavior_tree A simple python behavior tree library based on coroutines
  • btsk Behavior Tree Starter Kit
  • behave A behavior tree implementation in Python

Why another library so ?

SIMPLICITY

When you study behavior tree implementation, reactive node, dynamic change, runtime execution, etc ... At a moment you're build more or less something that mimic an evaluator 'eval/apply' or a compilator, with a complex hierarchical set of class.

All complexity came with internal state management, using tree of blackboard to avoid global variable, multithreading issue, maybe few callback etc ...

This break the simplicity and beauty of your initial design.

What I find useful with behavior tree:

  • clarity of expression
  • node tree representation
  • possibility to reuse behavior
  • add external measure to dynamicaly change a behavior, a first step on observable pattern...

As I've used OOP for years (very long time), I will try to avoid class tree and prefer using the power of functional programming to obtain what I want: add metadata on a semantic construction, deal with closure, use function in parameters or in return value...

And a last reason, more personal, it that i would explore python expressivity.

SO HOW ?

In this module, I propose using the concept of coroutines, and their mechanisms to manage the execution flow. By this way:

  • we reuse simple language idiom to manage state, parameter, etc
  • no design constraint on action implementation
  • most of language build block could be reused

You could build expression like this:

async def a_func():
    """A great function"""
    return "a"

async def b_decorator(child_value, other=""):
    """A great decorator..."""
    return f"b{child_value}{other}"

with BTreeRunner() as runner:
    assert runner.run(decorate(a_func, b_decorator)) == "ba"

This expression apply b_decorator on function a_func. Note that decorate(a_func, b_decorator) is not an async function, only action, or condition are async function.

Few guidelines of this implementation:

  • In order to mimic all NodeStatus (success, failure, running), I replace this by truthy/falsy meaning of evaluation value. A special dedicated exception decorate standard exception in order to give them a Falsy meaning (ControlFlowException). By default, exception are raised like happen usually until you catch them or decorate your function with ignore_exception.
  • Blackboard pattern, act as a manager of context variable for behavior tree. With python 3, please... simply use contextvars !
  • In order to be able to build a semantic tree, I've introduce a metadata tuple added on function implementation.

The rest is just implementation details..

A little note:

You should not use this until you're ready to think about what you're doing :)

Note about 'async' framework

As we use async function as underlaying mechanism to manage the execution flow, the standard library asyncio is pretty fine. But, (always a but somewhere isn't it...), you should read this amazing blog post by Nathaniel J. Smith. And next study curio framework in deep.

As curio say:

Don't Use Curio if You're Allergic to Curio

Personaly, after few time of testing and reading curio code, I'm pretty addict.

If curio is not present, we default to asyncio.

Installation

Install with pip or uv:

  • pip install async-btree or uv add async-btree
  • with curio extension: pip install async-btree[curio] or uv add async-btree[curio]

Usage

See API Reference documentation.

Examples:

With this framework, you didn't find any configuration file, no Xml, no json, no yaml.

The main reason (oriented and personal point of view) is that you did not need to introduce an extra level of abstraction to declare a composition of functions. I think it's true for most of main use case (except using an editor to wrote behaviour tree for example).

So "If you wrote your function with python, wrote composition in python"... (remember that you did not need XML to do SQL, just write good sql...)

So, the goal is to:

  • define your business function which implements actions or conditions, with all test case that you wish/need
  • compose them using those provided by this framework like sequence, selector, ...
  • use them as it is or create a well define python module to reuse them

Want an abstract tree of our behaviour tree ?

Functions from async-btree build an abstract tree for you. If you lookup in code, you should see an annotation "node_metadata" on internal implementation. This decorator add basic information like function name, parameters, and children relation ship.

This abstract tree can be retrieved and stringified with analyze and stringify_analyze. Here the profile:

  def analyze(target: CallableFunction) -> Node: # here we have our "abstract tree code"
    ...

For example:

# your behaviour tree, or a sub tree:
my_func = alias(child=repeat_until(child=action(hello), condition=success_until_zero), name="btree_1")

# retrieve meta information and build a Node tree
abstract_tree_tree_1 = analyze(my_func) 

# output the tree:
print(stringify_analyze(abstract_tree_tree_1))

This should print:

 --> btree_1:
     --(child)--> repeat_until:
         --(condition)--> success_until_zero:
         --(child)--> action:
                      target: hello

Note about action and condition method:

  • you could use sync or async function
  • you could specify a return value with SUCCESS or FAILURE
  • function with no return value will be evaluated as FAILURE until you decorate them with a always_successor always_failure

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

async_btree-2.0.0.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

async_btree-2.0.0-py3-none-any.whl (17.6 kB view details)

Uploaded Python 3

File details

Details for the file async_btree-2.0.0.tar.gz.

File metadata

  • Download URL: async_btree-2.0.0.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.5.18

File hashes

Hashes for async_btree-2.0.0.tar.gz
Algorithm Hash digest
SHA256 6f5eb1fcd192a3db53e7f6ea76b364f9290f225b162d53eec52d13f97097f204
MD5 c31cc2c50017d7a108277af04f3b2403
BLAKE2b-256 cccb6d74035f0f8853dd84109c459381e7f719816ee1b0dd4d8340970179ffb5

See more details on using hashes here.

File details

Details for the file async_btree-2.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for async_btree-2.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e90e86c8b4c2b38483a4e172ad0f55e1311811cdd0b6043db099e6021d191e70
MD5 4ce65e86e7a03248f0756fda0e1820d4
BLAKE2b-256 00837395afe279683986ab4af6d008ef1b425ce9c297d30030d3962452fd2d61

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page