├── docs ├── authors.rst ├── history.rst ├── readme.rst ├── contributing.rst ├── index.rst ├── Makefile ├── make.bat ├── installation.rst ├── custom.rst ├── create_agent.rst ├── lists.rst ├── plans.rst ├── communication.rst ├── conf.py └── agentspeak.rst ├── requirements.txt ├── examples ├── send_achieve_param │ ├── slave.asl │ ├── master.asl │ └── run_example.py ├── tell_belief │ ├── slave.asl │ ├── master.asl │ └── run_example.py ├── ask_how │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── actions │ ├── actions.asl │ └── run_example.py ├── send_achieve │ ├── slave.asl │ ├── master.asl │ └── run_example.py ├── sender_receiver │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── tell_how │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── unachieve_no_recursive │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── basic │ ├── basic.asl │ └── run_example.py ├── untell │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── untell_how │ ├── sender.asl │ ├── receiver.asl │ └── run_example.py ├── unachieve_while │ ├── receiver.asl │ ├── sender.asl │ └── run_example.py ├── counter │ ├── counter.asl │ └── run_example.py ├── master_slave │ ├── slave.asl │ ├── master.asl │ └── run_example.py └── run_all.py ├── tests └── test_spade_bdi.py ├── AUTHORS.rst ├── requirements_dev.txt ├── spade_bdi ├── __init__.py └── bdi.py ├── MANIFEST.in ├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── setup.cfg ├── tox.ini ├── .travis.yml ├── readthedocs.yml ├── LICENSE ├── HISTORY.rst ├── .gitignore ├── setup.py ├── Makefile ├── README.rst └── CONTRIBUTING.rst /docs/authors.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../AUTHORS.rst 2 | -------------------------------------------------------------------------------- /docs/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../HISTORY.rst 2 | -------------------------------------------------------------------------------- /docs/readme.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | -------------------------------------------------------------------------------- /docs/contributing.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CONTRIBUTING.rst 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | spade>=4.0.1 2 | agentspeak>=0.2.1 3 | loguru>=0.7.2 4 | -------------------------------------------------------------------------------- /examples/send_achieve_param/slave.asl: -------------------------------------------------------------------------------- 1 | +!say(M) <- .print("master has said: ", M). 2 | -------------------------------------------------------------------------------- /examples/tell_belief/slave.asl: -------------------------------------------------------------------------------- 1 | +!hello <- 2 | ?name(X); 3 | .print("Hello", X). 4 | -------------------------------------------------------------------------------- /examples/ask_how/receiver.asl: -------------------------------------------------------------------------------- 1 | 2 | +!hello(N) <- 3 | .print("This is the hello plan from", N). 4 | -------------------------------------------------------------------------------- /examples/actions/actions.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | .my_function(4, R); 5 | .my_action(R). 6 | 7 | -------------------------------------------------------------------------------- /examples/send_achieve/slave.asl: -------------------------------------------------------------------------------- 1 | +!hello <- .print("Hello from slave"). 2 | 3 | +!bye <- .print("Goodbye from slave"). 4 | -------------------------------------------------------------------------------- /examples/sender_receiver/receiver.asl: -------------------------------------------------------------------------------- 1 | +!hello(Msg)[source(Sender)] 2 | <- 3 | .print("got a message from", Sender, "saying:\n", Msg). -------------------------------------------------------------------------------- /examples/tell_how/receiver.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | meet("Paul"). 4 | 5 | +!start <- 6 | .print("Waiting for start message..."). 7 | 8 | -------------------------------------------------------------------------------- /examples/unachieve_no_recursive/receiver.asl: -------------------------------------------------------------------------------- 1 | +!welcome(N) <- 2 | .wait(400); 3 | .print("Welcome", N); 4 | !!welcome(N) 5 | . 6 | -------------------------------------------------------------------------------- /examples/basic/basic.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | +car(rojo); 5 | +truck(azul). 6 | 7 | +car(Color) 8 | <- .print("El carro es ",Color). -------------------------------------------------------------------------------- /tests/test_spade_bdi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """Tests for `spade_bdi` package.""" 5 | 6 | import pytest 7 | 8 | 9 | -------------------------------------------------------------------------------- /examples/tell_belief/master.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start : true 4 | <- 5 | ?slave1(X); 6 | .send(X, tell, name("JohnDoe")); 7 | .send(X, achieve, hello). 8 | -------------------------------------------------------------------------------- /examples/untell/receiver.asl: -------------------------------------------------------------------------------- 1 | 2 | +!check : name("John") <- .print("Yes, there is a John"). 3 | 4 | +!check : not name("John") <- .print("No, there is no such thing as John"). 5 | -------------------------------------------------------------------------------- /examples/send_achieve_param/master.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | ?slave1(X); 5 | .print("Sending message to slave1"); 6 | .send(X, achieve, say("Hello BDI")); 7 | .print("Message sent"). 8 | -------------------------------------------------------------------------------- /examples/sender_receiver/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start 4 | <- 5 | ?receiver(X); 6 | .print("sending a message ..."); 7 | .send(X, achieve, hello("Hello World!")); 8 | .print("sent a message"). 9 | -------------------------------------------------------------------------------- /examples/send_achieve/master.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | ?slave1(X); 5 | .print("Sending message"); 6 | .send(X, achieve, hello); 7 | .print("Message sent"); 8 | .send(X, achieve, bye). 9 | -------------------------------------------------------------------------------- /examples/unachieve_no_recursive/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | ?receiver(X); 5 | .send(X, achieve, welcome(1)); 6 | .wait(2000); 7 | .send(X, unachieve, welcome(N)); 8 | .print("Unachieved"). 9 | -------------------------------------------------------------------------------- /examples/untell_how/sender.asl: -------------------------------------------------------------------------------- 1 | 2 | !start. 3 | 4 | +!start 5 | <- 6 | ?receiver(X); 7 | .wait(1000); 8 | .print("Make the receiver forget about the plan"); 9 | .send(X, untellHow, "@custom_tag"). 10 | -------------------------------------------------------------------------------- /AUTHORS.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | Credits 3 | ======= 4 | 5 | Development Lead 6 | ---------------- 7 | 8 | * Sergio Frayle Pérez 9 | 10 | Contributors 11 | ------------ 12 | 13 | * Javi Palanca 14 | -------------------------------------------------------------------------------- /examples/ask_how/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start 4 | <- 5 | ?receiver(X); 6 | .print("Ask for Plan"); 7 | .send(X, askHow, "+!hello(N)"); 8 | .print("Plan added..."); 9 | .wait(1000); 10 | !hello(X) 11 | . 12 | -------------------------------------------------------------------------------- /examples/unachieve_while/receiver.asl: -------------------------------------------------------------------------------- 1 | +!welcome(N) <- 2 | while (true) { 3 | .wait(500); 4 | .print("Welcome", N); 5 | }. 6 | 7 | +!welcome(N, M) <- 8 | while (true) { 9 | .wait(500); 10 | .print("Welcome", N, "-", M); 11 | }. 12 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | bump2version==1.0.1 2 | wheel==0.37.1 3 | watchdog==2.1.9 4 | flake8==3.5.0 5 | tox==4.23.2 6 | coverage==7.2.1 7 | Sphinx==5.1.0 8 | twine==3.8.0 9 | pytest==8.3.4 10 | pytest-runner==5.3.2 11 | sphinx-rtd-theme==1.0.0 12 | click==8.1.7 -------------------------------------------------------------------------------- /examples/untell_how/receiver.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | .print("Starting..."); 5 | !plan1. 6 | 7 | @custom_tag 8 | +!plan1 <- 9 | .print("I'm a plan"); 10 | .wait(100); 11 | !plan1. 12 | 13 | +!plan1 <- 14 | .print("Ending example"). 15 | -------------------------------------------------------------------------------- /spade_bdi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from . import bdi 3 | 4 | """Top-level package for Spade-BDI.""" 5 | 6 | __author__ = """Sergio Frayle Pérez""" 7 | __email__ = 'sfp932705@gmail.com' 8 | __version__ = '0.3.2' 9 | 10 | __all__ = ["bdi"] 11 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include AUTHORS.rst 2 | include CONTRIBUTING.rst 3 | include HISTORY.rst 4 | include LICENSE 5 | include README.rst 6 | 7 | recursive-include tests * 8 | recursive-exclude * __pycache__ 9 | recursive-exclude * *.py[co] 10 | 11 | recursive-include docs *.rst conf.py Makefile make.bat *.jpg *.png *.gif 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | 20 | [Makefile] 21 | indent_style = tab 22 | -------------------------------------------------------------------------------- /examples/tell_how/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start : true 4 | <- 5 | ?receiver(X); 6 | .print("Telling plan to receiver"); 7 | .wait(2000); 8 | .send(X, tellHow, "+!hello(N) : not meet(\"John\") & meet(\"Paul\") <- .print(\"I am a plan called \", N)."); 9 | .wait(500); 10 | .send(X, achieve, hello("Ringo")); 11 | .send(X, achieve, hello("Paul")). 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Spade-BDI version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /examples/untell/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start : true 4 | <- 5 | ?receiver(X); 6 | .send(X,tell, name("John")); 7 | .send(X, achieve, check); 8 | .send(X, untell, name("Andrew")); 9 | .print("Disproving a belief that does not exist..."); 10 | .send(X, achieve, check); 11 | .send(X, untell, name("John")); 12 | .print("Disproving a belief that does exist..."); 13 | .send(X, achieve, check). 14 | -------------------------------------------------------------------------------- /examples/unachieve_while/sender.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start <- 4 | ?receiver(X); 5 | .print("start achievement ..."); 6 | .send(X, achieve, welcome(2)); 7 | .print("start achievement ..."); 8 | .send(X, achieve, welcome(3)); 9 | .wait(100); 10 | .print("start achievement ..."); 11 | .send(X, achieve, welcome(3, 2)); 12 | .wait(2000); 13 | .print("cancel achievent ..."); 14 | .send(X, unachieve, welcome(N, M)); 15 | .print("cancelled."). 16 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Welcome to Spade-BDI's documentation! 2 | ====================================== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | :caption: Contents: 7 | 8 | readme 9 | installation 10 | create_agent 11 | agentspeak 12 | communication 13 | plans 14 | lists 15 | custom 16 | modules 17 | contributing 18 | authors 19 | history 20 | 21 | Indices and tables 22 | ================== 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.3.2 3 | commit = True 4 | tag = False 5 | 6 | [bumpversion:file:setup.py] 7 | search = version='{current_version}' 8 | replace = version='{new_version}' 9 | 10 | [bumpversion:file:spade_bdi/__init__.py] 11 | search = __version__ = '{current_version}' 12 | replace = __version__ = '{new_version}' 13 | 14 | [bdist_wheel] 15 | universal = 1 16 | 17 | [flake8] 18 | exclude = docs 19 | 20 | [aliases] 21 | test = pytest 22 | 23 | [tool:pytest] 24 | collect_ignore = ['setup.py'] 25 | 26 | -------------------------------------------------------------------------------- /examples/counter/counter.asl: -------------------------------------------------------------------------------- 1 | counter(8). 2 | type(dec). 3 | !init. 4 | 5 | +!init 6 | <- 7 | .print("Starting...."); 8 | !obj2. 9 | 10 | 11 | +!obj2: type(inc) 12 | <- 13 | .print("Increasing"); 14 | ?counter(X); 15 | -+counter(X+1); 16 | .wait(100); 17 | !obj2. 18 | 19 | 20 | +!obj2: type(dec) 21 | <- 22 | .print("Decreasing"); 23 | ?counter(X); 24 | -+counter(X-1); 25 | .wait(100); 26 | !obj2. 27 | 28 | 29 | +!obj2: not type(_) 30 | <- 31 | .print("Waiting"); 32 | .wait(100); 33 | !obj2. 34 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py36, flake8 3 | 4 | [travis] 5 | python = 6 | 3.6: py36 7 | 8 | [testenv:flake8] 9 | basepython = python 10 | deps = flake8 11 | commands = flake8 spade_bdi 12 | 13 | [testenv] 14 | setenv = 15 | PYTHONPATH = {toxinidir} 16 | deps = 17 | -r{toxinidir}/requirements_dev.txt 18 | ; If you want to make tox run the tests with the same versions, create a 19 | ; requirements.txt with the pinned versions and uncomment the following line: 20 | ; -r{toxinidir}/requirements.txt 21 | commands = 22 | pip install -U pip 23 | py.test --basetemp={envtmpdir} 24 | 25 | 26 | -------------------------------------------------------------------------------- /examples/master_slave/slave.asl: -------------------------------------------------------------------------------- 1 | counter(0). 2 | 3 | +increase(Inc)[source(S)]: master(M) & .substring(M,S,R) 4 | <- 5 | .print("increasing"); 6 | ?counter(X); 7 | .print(X); 8 | -+counter(X+Inc). 9 | 10 | +decrease(Dec)[source(S)]: master(M) & .substring(M,S,R) 11 | <- 12 | .print("decreasing"); 13 | ?counter(X); 14 | .print(X); 15 | -+counter(X-Dec). 16 | 17 | -increase(Inc)[source(S)]: master(M) & .substring(M,S,R) 18 | <- 19 | .print("DELETING increase BELIEF from an untell message"). 20 | 21 | -decrease(Dec)[source(S)]: master(M) & .substring(M,S,R) 22 | <- 23 | .print("DELETING decrease BELIEF from an untell message"). 24 | -------------------------------------------------------------------------------- /examples/master_slave/master.asl: -------------------------------------------------------------------------------- 1 | !start. 2 | 3 | +!start 4 | <- 5 | -start; 6 | !obj2. 7 | 8 | +!obj2: type(inc) 9 | <- 10 | ?slave1(X); 11 | ?slave2(Y); 12 | .send(X, tell, increase(2)); 13 | .send(Y, tell, increase(5)); 14 | .wait(300); 15 | !obj2. 16 | 17 | +!obj2: type(dec) 18 | <- 19 | ?slave1(X); 20 | ?slave2(Y); 21 | .send(X, tell, decrease(2)); 22 | .send(Y, tell, decrease(5)); 23 | .wait(300); 24 | !obj2. 25 | 26 | +!obj2: not type(_) 27 | <- 28 | ?slave1(X); 29 | ?slave2(Y); 30 | .print("Finishing"); 31 | .send(X, untell, increase(2)); 32 | .send(Y, untell, increase(5)); 33 | .send(X, untell, decrease(2)); 34 | .send(Y, untell, decrease(5)). 35 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = spade_bdi 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Config file for automatic testing at travis-ci.org 2 | 3 | language: python 4 | python: 5 | - 3.6 6 | 7 | # Command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors 8 | install: pip install -U tox-travis 9 | 10 | # Command to run tests, e.g. python setup.py test 11 | script: tox 12 | 13 | # Assuming you have installed the travis-ci CLI tool, after you 14 | # create the Github repo and add it to Travis, run the 15 | # following command to finish PyPI deployment setup: 16 | # $ travis encrypt --add deploy.password 17 | #deploy: 18 | # provider: pypi 19 | # distributions: sdist bdist_wheel 20 | # user: sfp932705 21 | # password: 22 | # secure: PLEASE_REPLACE_ME 23 | # on: 24 | # tags: true 25 | # repo: sfp932705/spade_bdi 26 | # python: 3.6 27 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | build: 4 | os: ubuntu-22.04 5 | tools: 6 | python: "3.12" 7 | 8 | sphinx: 9 | configuration: docs/conf.py 10 | # You can configure Sphinx to use a different builder, for instance use the dirhtml builder for simpler URLs 11 | # builder: "dirhtml" 12 | # Fail on all warnings to avoid broken references 13 | # fail_on_warning: true 14 | 15 | # Optionally build your docs in additional formats such as PDF and ePub 16 | formats: 17 | - pdf 18 | - epub 19 | 20 | # Optional but recommended, declare the Python requirements required 21 | # to build your documentation 22 | # See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html 23 | python: 24 | install: 25 | - requirements: requirements.txt 26 | - requirements: requirements_dev.txt 27 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=python -msphinx 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=spade_bdi 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The Sphinx module was not found. Make sure you have Sphinx installed, 20 | echo.then set the SPHINXBUILD environment variable to point to the full 21 | echo.path of the 'sphinx-build' executable. Alternatively you may add the 22 | echo.Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025, Javi Palanca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /examples/sender_receiver/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | 5 | import spade 6 | 7 | from spade_bdi.bdi import BDIAgent 8 | 9 | 10 | async def main(server, password): 11 | receiver = BDIAgent("BDIReceiverAgent@" + server, password, "receiver.asl") 12 | await receiver.start() 13 | 14 | sender = BDIAgent("BDISenderAgent@" + server, password, "sender.asl") 15 | sender.bdi.set_belief("receiver", "BDIReceiverAgent@" + server) 16 | await sender.start() 17 | 18 | await asyncio.sleep(1.5) 19 | await sender.stop() 20 | await receiver.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/send_achieve/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("slave_1@{}".format(server), password, "slave.asl") 11 | b.bdi.set_belief("master", "master@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("master@{}".format(server), password, "master.asl") 15 | a.bdi.set_belief("slave1", "slave_1@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(2) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/tell_belief/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("slave_1@{}".format(server), password, "slave.asl") 11 | b.bdi.set_belief("master", "master@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("master@{}".format(server), password, "master.asl") 15 | a.bdi.set_belief("slave1", "slave_1@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(2) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/untell_how/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 11 | b.bdi.set_belief("sender", "sender@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("sender@{}".format(server), password, "sender.asl") 15 | a.bdi.set_belief("receiver", "receiver@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(3) 19 | await a.stop() 20 | await b.stop() 21 | 22 | if __name__ == "__main__": 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument("--server", help="XMPP Server") 25 | parser.add_argument("--password", help="Password") 26 | args = parser.parse_args() 27 | 28 | if args.server is None: 29 | server = input("XMPP Server> ") 30 | else: 31 | server = args.server 32 | 33 | if args.password is None: 34 | passwd = getpass.getpass() 35 | else: 36 | passwd = args.password 37 | spade.run(main(server, passwd)) 38 | -------------------------------------------------------------------------------- /examples/tell_how/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | a = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 11 | a.bdi.set_belief("sender", "sender@{}".format(server)) 12 | await a.start() 13 | 14 | b = BDIAgent("sender@{}".format(server), password, "sender.asl") 15 | b.bdi.set_belief("receiver", "receiver@{}".format(server)) 16 | await b.start() 17 | 18 | await asyncio.sleep(5) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/untell/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 11 | b.bdi.set_belief("sender", "sender@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("sender@{}".format(server), password, "sender.asl") 15 | a.bdi.set_belief("receiver", "receiver@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(1) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/send_achieve_param/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("slave_1@{}".format(server), password, "slave.asl") 11 | b.bdi.set_belief("master", "master@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("master@{}".format(server), password, "master.asl") 15 | a.bdi.set_belief("slave1", "slave_1@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(2) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/unachieve_while/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 11 | b.bdi.set_belief("sender", "sender@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("sender@{}".format(server), password, "sender.asl") 15 | a.bdi.set_belief("receiver", "receiver@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(5) 19 | await a.stop() 20 | await b.stop() 21 | 22 | 23 | if __name__ == "__main__": 24 | parser = argparse.ArgumentParser() 25 | parser.add_argument("--server", help="XMPP Server") 26 | parser.add_argument("--password", help="Password") 27 | args = parser.parse_args() 28 | 29 | if args.server is None: 30 | server = input("XMPP Server> ") 31 | else: 32 | server = args.server 33 | 34 | if args.password is None: 35 | passwd = getpass.getpass() 36 | else: 37 | passwd = args.password 38 | spade.run(main(server, passwd)) 39 | -------------------------------------------------------------------------------- /examples/ask_how/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | 5 | import spade 6 | 7 | from spade_bdi.bdi import BDIAgent 8 | 9 | 10 | async def main(server, password): 11 | b = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 12 | b.bdi.set_belief("sender", "sender@{}".format(server)) 13 | await b.start() 14 | 15 | a = BDIAgent("sender@{}".format(server), password, "sender.asl") 16 | a.bdi.set_belief("receiver", "receiver@{}".format(server)) 17 | await a.start() 18 | 19 | await asyncio.sleep(5) 20 | 21 | await b.stop() 22 | await a.stop() 23 | 24 | 25 | if __name__ == "__main__": 26 | parser = argparse.ArgumentParser() 27 | parser.add_argument("--server", help="XMPP Server") 28 | parser.add_argument("--password", help="Password") 29 | args = parser.parse_args() 30 | 31 | if args.server is None: 32 | server = input("XMPP Server> ") 33 | else: 34 | server = args.server 35 | 36 | if args.password is None: 37 | passwd = getpass.getpass() 38 | else: 39 | passwd = args.password 40 | spade.run(main(server, passwd)) 41 | -------------------------------------------------------------------------------- /examples/unachieve_no_recursive/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | import spade 5 | 6 | from spade_bdi.bdi import BDIAgent 7 | 8 | 9 | async def main(server, password): 10 | b = BDIAgent("receiver@{}".format(server), password, "receiver.asl") 11 | b.bdi.set_belief("sender", "sender@{}".format(server)) 12 | await b.start() 13 | 14 | a = BDIAgent("sender@{}".format(server), password, "sender.asl") 15 | a.bdi.set_belief("receiver", "receiver@{}".format(server)) 16 | await a.start() 17 | 18 | await asyncio.sleep(5) 19 | await a.stop() 20 | await b.stop() 21 | 22 | if __name__ == "__main__": 23 | parser = argparse.ArgumentParser() 24 | parser.add_argument("--server", help="XMPP Server") 25 | parser.add_argument("--password", help="Password") 26 | args = parser.parse_args() 27 | 28 | if args.server is None: 29 | server = input("XMPP Server> ") 30 | else: 31 | server = args.server 32 | 33 | if args.password is None: 34 | passwd = getpass.getpass() 35 | else: 36 | passwd = args.password 37 | spade.run(main(server, passwd)) 38 | -------------------------------------------------------------------------------- /examples/basic/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | 5 | import spade 6 | 7 | from spade_bdi.bdi import BDIAgent 8 | 9 | 10 | async def main(server, password): 11 | a = BDIAgent(f"bdiagent@{server}", password, "basic.asl") 12 | await a.start() 13 | 14 | await asyncio.sleep(1) 15 | 16 | a.bdi.set_belief("car", "azul", "big") 17 | a.bdi.print_beliefs() 18 | print("GETTING FIRST CAR BELIEF") 19 | print(a.bdi.get_belief("car")) 20 | a.bdi.print_beliefs() 21 | a.bdi.remove_belief("car", 'azul', "big") 22 | a.bdi.print_beliefs() 23 | print(a.bdi.get_beliefs()) 24 | a.bdi.set_belief("car", 'amarillo') 25 | 26 | await a.stop() 27 | 28 | 29 | if __name__ == "__main__": 30 | parser = argparse.ArgumentParser() 31 | parser.add_argument("--server", help="XMPP Server") 32 | parser.add_argument("--password", help="Password") 33 | args = parser.parse_args() 34 | 35 | if args.server is None: 36 | server = input("XMPP Server> ") 37 | else: 38 | server = args.server 39 | 40 | if args.password is None: 41 | passwd = getpass.getpass() 42 | else: 43 | passwd = args.password 44 | spade.run(main(server, passwd)) 45 | -------------------------------------------------------------------------------- /examples/run_all.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import getpass 3 | import subprocess 4 | 5 | examples = [ 6 | "basic", 7 | "actions", 8 | "counter", 9 | "sender_receiver", 10 | "master_slave", 11 | "ask_how", 12 | "send_achieve", 13 | "send_achieve_param", 14 | "tell_belief", 15 | "tell_how", 16 | "unachieve_no_recursive", 17 | "unachieve_while", 18 | "untell", 19 | "untell_how", 20 | ] 21 | if __name__ == "__main__": 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument("--server", help="XMPP Server") 24 | parser.add_argument("--password", help="Password") 25 | args = parser.parse_args() 26 | 27 | if args.server is None: 28 | server = input("XMPP Server> ") 29 | else: 30 | server = args.server 31 | 32 | if args.password is None: 33 | passwd = getpass.getpass() 34 | else: 35 | passwd = args.password 36 | 37 | for example in examples: 38 | l = 20 + len(example) 39 | print(l*"*" + "\n" + f"* Running {example} example *" + "\n" + l*"*") 40 | # Run example using subprocess 41 | subprocess.call(args=["python", "run_example.py", "--server", f"{server}", "--password", f"{passwd}"], 42 | cwd=f"./{example}") 43 | print("Finished {}".format(example)) 44 | -------------------------------------------------------------------------------- /examples/actions/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | 5 | import agentspeak 6 | import spade 7 | 8 | from spade_bdi.bdi import BDIAgent 9 | 10 | 11 | class MyCustomBDIAgent(BDIAgent): 12 | def add_custom_actions(self, actions): 13 | @actions.add_function(".my_function", (int,)) 14 | def _my_function(x): 15 | return x * x 16 | 17 | @actions.add(".my_action", 1) 18 | def _my_action(agent, term, intention): 19 | arg = agentspeak.grounded(term.args[0], intention.scope) 20 | print(arg) 21 | yield 22 | 23 | 24 | async def main(server, password): 25 | a = MyCustomBDIAgent(f"bdiagent@{server}", password, "actions.asl") 26 | 27 | await a.start() 28 | await asyncio.sleep(2) 29 | await a.stop() 30 | 31 | 32 | if __name__ == "__main__": 33 | parser = argparse.ArgumentParser() 34 | parser.add_argument("--server", help="XMPP Server") 35 | parser.add_argument("--password", help="Password") 36 | args = parser.parse_args() 37 | 38 | if args.server is None: 39 | server = input("XMPP Server> ") 40 | else: 41 | server = args.server 42 | 43 | if args.password is None: 44 | passwd = getpass.getpass() 45 | else: 46 | passwd = args.password 47 | spade.run(main(server, passwd)) 48 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Installation 5 | ============ 6 | 7 | 8 | Stable release 9 | -------------- 10 | 11 | To install Spade-BDI, run this command in your terminal: 12 | 13 | .. code-block:: console 14 | 15 | $ pip install spade_bdi 16 | 17 | This is the preferred method to install Spade-BDI, as it will always install the most recent stable release. 18 | 19 | If you don't have `pip`_ installed, this `Python installation guide`_ can guide 20 | you through the process. 21 | 22 | .. _pip: https://pip.pypa.io 23 | .. _Python installation guide: http://docs.python-guide.org/en/latest/starting/installation/ 24 | 25 | 26 | From sources 27 | ------------ 28 | 29 | The sources for Spade-BDI can be downloaded from the `Github repo`_. 30 | 31 | You can either clone the public repository: 32 | 33 | .. code-block:: console 34 | 35 | $ git clone git://github.com/javipalanca/spade_bdi 36 | 37 | Or download the `tarball`_: 38 | 39 | .. code-block:: console 40 | 41 | $ curl -OL https://github.com/javipalanca/spade_bdi/tarball/master 42 | 43 | Once you have a copy of the source, you can install it with: 44 | 45 | .. code-block:: console 46 | 47 | $ python setup.py install 48 | 49 | 50 | .. _Github repo: https://github.com/javipalanca/spade_bdi 51 | .. _tarball: https://github.com/javipalanca/spade_bdi/tarball/master 52 | -------------------------------------------------------------------------------- /docs/custom.rst: -------------------------------------------------------------------------------- 1 | 2 | =================================== 3 | Create custom actions and functions 4 | =================================== 5 | 6 | You must to overload the ``add_custom_actions`` method and to use the ``add_function`` or ``add`` (for actions) decorator. 7 | This custom method receives always the ``actions`` parameter:: 8 | 9 | import spade_bdi 10 | 11 | class MyCustomBDIAgent(BDIAgent): 12 | 13 | def add_custom_actions(self, actions): 14 | @actions.add_function(".my_function", (int,)) 15 | def _my_function(x): 16 | return x * x 17 | 18 | @actions.add(".my_action", 1) 19 | def _my_action(agent, term, intention): 20 | arg = agentspeak.grounded(term.args[0], intention.scope) 21 | print(arg) 22 | yield 23 | 24 | 25 | 26 | 27 | .. hint:: Adding a function requires to call the ``add_function`` decorator with two parameters: the name of the function (starting with a dot) 28 | and a tuple with the types of the parameters (e.g. ``(int, str)``). 29 | 30 | .. hint:: Adding an action requires to call the ``add`` decorator with two parameters: the name of the action (starting with a dot) 31 | and the number of parameters. Also, the method being decorated receives three parameters: ``agent``, ``term,`` and ``intention``. 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.3.2 (2025-03-01) 6 | ------------------ 7 | 8 | * Updated to SPADE 4.0.1 9 | * Updated dependencies 10 | * License changed to MIT 11 | 12 | 0.3.1 (2024-01-06) 13 | ------------------ 14 | 15 | * Added new examples 16 | * Added documentation 17 | * Updated to SPADE 3.3.2 18 | 19 | 0.3.0 (2023-06-13) 20 | ------------------ 21 | 22 | * Updated to SPADE 3.3.0. 23 | 24 | 0.2.2 (2022-06-03) 25 | ------------------ 26 | 27 | * Added exception when belief is not initialized. 28 | * Improved examples. 29 | * Improved documentation. 30 | 31 | 0.2.1 (2020-04-13) 32 | ------------------ 33 | 34 | * Fixed a bug when updating beliefs. 35 | * Upgraded spade version to 3.1.4. 36 | 37 | 0.2.0 (2020-02-24) 38 | ------------------ 39 | 40 | * Created add_custom_actions method. 41 | * Added example for actions. 42 | * Improved documentation. 43 | * Added some helpers like pause_bdi, resume_bdi. 44 | * Now the asl file in the constructor is mandatory. 45 | 46 | 0.1.4 (2019-07-10) 47 | ------------------ 48 | 49 | * Allow to send messages to JIDs stored as beliefs. 50 | 51 | 0.1.3 (2019-07-08) 52 | ------------------ 53 | 54 | * Allow .send to a list of receivers. 55 | * Allow to receive messages with lists of lists. 56 | * Fixed readme. 57 | 58 | 0.1.1 (2019-06-18) 59 | ------------------ 60 | 61 | * Moved from pyson to python-agentspeak 62 | * Added some helpers like pause_bdi, resume_bdi. 63 | * Now the asl file in the constructor is mandatory. 64 | * Allow to send tell messages with no args. 65 | * Allow sending messages with variables. 66 | * Extended the examples. 67 | 68 | 0.1.0 (2019-03-09) 69 | ------------------ 70 | 71 | * First release on PyPI. 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | """The setup script.""" 5 | 6 | from setuptools import setup, find_packages 7 | 8 | 9 | def parse_requirements(filename): 10 | """ load requirements from a pip requirements file """ 11 | lineiter = (line.strip() for line in open(filename)) 12 | return [line for line in lineiter if line and not line.startswith("#")] 13 | 14 | 15 | with open('README.rst') as readme_file: 16 | readme = readme_file.read() 17 | 18 | with open('HISTORY.rst') as history_file: 19 | history = history_file.read() 20 | 21 | requirements = parse_requirements("requirements.txt") 22 | 23 | setup_requirements = ['pytest-runner', ] 24 | 25 | test_requirements = parse_requirements("requirements_dev.txt") 26 | 27 | setup( 28 | author="Sergio Frayle Pérez", 29 | author_email='sfp932705@gmail.com', 30 | classifiers=[ 31 | 'Development Status :: 4 - Beta', 32 | 'Intended Audience :: Developers', 33 | 'License :: OSI Approved :: MIT License', 34 | 'Natural Language :: English', 35 | 'Programming Language :: Python :: 3', 36 | 'Programming Language :: Python :: 3.8', 37 | 'Programming Language :: Python :: 3.9', 38 | 'Programming Language :: Python :: 3.10', 39 | 'Programming Language :: Python :: 3.11', 40 | 'Programming Language :: Python :: 3.12', 41 | 'Operating System :: MacOS :: MacOS X', 42 | 'Operating System :: POSIX :: Linux', 43 | 'Operating System :: Microsoft :: Windows', 44 | 'Topic :: Scientific/Engineering :: Artificial Intelligence', 45 | 'Topic :: Internet :: XMPP', 46 | ], 47 | description="Plugin for SPADE 3 MAS platform to implement BDI Agents.", 48 | install_requires=requirements, 49 | license="MIT License v3", 50 | long_description=readme + '\n\n' + history, 51 | include_package_data=True, 52 | keywords='spade_bdi', 53 | name='spade_bdi', 54 | packages=find_packages(include=['spade_bdi']), 55 | setup_requires=setup_requirements, 56 | test_suite='tests', 57 | tests_require=test_requirements, 58 | url='https://github.com/javipalanca/spade_bdi', 59 | version='0.3.2', 60 | zip_safe=False, 61 | ) 62 | -------------------------------------------------------------------------------- /docs/create_agent.rst: -------------------------------------------------------------------------------- 1 | ============================= 2 | Creating a BDI Agent in SPADE 3 | ============================= 4 | 5 | Belief-Desire-Intention (BDI) agents are a cornerstone of modern agent-based systems. 6 | In SPADE (Smart Python multi-Agent Development Environment), creating a BDI agent involves managing the agent's beliefs, desires, and intentions in a dynamic environment. 7 | This section provides a guide on setting up and managing a BDI agent in SPADE. 8 | 9 | Initial Setup of a BDI Agent 10 | ============================ 11 | 12 | 1. **Agent Creation**: 13 | - To create a BDI agent, you need to define its Jabber Identifier (JID) and password. The agent is also associated with an AgentSpeak file that defines its initial behaviors. 14 | 15 | - **Initialization Code**: 16 | :: 17 | 18 | from spade import BDIAgent 19 | agent = BDIAgent("youragent@yourserver.com", "password", "initial_plan.asl") 20 | await agent.start() 21 | 22 | 2. **Defining Initial Beliefs**: 23 | 24 | - The initial beliefs of the agent can be defined in the AgentSpeak file or programmatically set after the agent starts. 25 | 26 | Managing Beliefs 27 | ================ 28 | 29 | 1. **Setting Beliefs**: 30 | - Beliefs represent the agent's knowledge about the world and can be added or updated using the `set_belief` method. 31 | - **Example Code**: 32 | :: 33 | 34 | agent.bdi.set_belief("key", "value") 35 | 36 | 2. **Retrieving Beliefs**: 37 | - To access the current beliefs of the agent, use methods like `get_belief` or `get_beliefs`. 38 | - **Example Code**: 39 | :: 40 | 41 | current_belief = agent.bdi.get_belief("key") 42 | all_beliefs = agent.bdi.get_beliefs() 43 | 44 | 3. **Removing Beliefs**: 45 | - Beliefs can be dynamically removed using the `remove_belief` method. 46 | - **Example Code**: 47 | :: 48 | 49 | agent.bdi.remove_belief("key") 50 | 51 | Creating a BDI agent in SPADE involves initializing the agent with its credentials and defining its initial set of beliefs and plans. 52 | The agent's beliefs are dynamically managed, allowing it to adapt to changes in the environment. 53 | SPADE's framework offers a flexible and powerful platform for developing sophisticated BDI agents in multi-agent systems. 54 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean clean-test clean-pyc clean-build docs help 2 | .DEFAULT_GOAL := help 3 | 4 | define BROWSER_PYSCRIPT 5 | import os, webbrowser, sys 6 | 7 | try: 8 | from urllib import pathname2url 9 | except: 10 | from urllib.request import pathname2url 11 | 12 | webbrowser.open("file://" + pathname2url(os.path.abspath(sys.argv[1]))) 13 | endef 14 | export BROWSER_PYSCRIPT 15 | 16 | define PRINT_HELP_PYSCRIPT 17 | import re, sys 18 | 19 | for line in sys.stdin: 20 | match = re.match(r'^([a-zA-Z_-]+):.*?## (.*)$$', line) 21 | if match: 22 | target, help = match.groups() 23 | print("%-20s %s" % (target, help)) 24 | endef 25 | export PRINT_HELP_PYSCRIPT 26 | 27 | BROWSER := python -c "$$BROWSER_PYSCRIPT" 28 | 29 | help: 30 | @python -c "$$PRINT_HELP_PYSCRIPT" < $(MAKEFILE_LIST) 31 | 32 | clean: clean-build clean-pyc clean-test ## remove all build, test, coverage and Python artifacts 33 | 34 | clean-build: ## remove build artifacts 35 | rm -fr build/ 36 | rm -fr dist/ 37 | rm -fr .eggs/ 38 | find . -name '*.egg-info' -exec rm -fr {} + 39 | find . -name '*.egg' -exec rm -f {} + 40 | 41 | clean-pyc: ## remove Python file artifacts 42 | find . -name '*.pyc' -exec rm -f {} + 43 | find . -name '*.pyo' -exec rm -f {} + 44 | find . -name '*~' -exec rm -f {} + 45 | find . -name '__pycache__' -exec rm -fr {} + 46 | 47 | clean-test: ## remove test and coverage artifacts 48 | rm -fr .tox/ 49 | rm -f .coverage 50 | rm -fr htmlcov/ 51 | rm -fr .pytest_cache 52 | 53 | lint: ## check style with flake8 54 | flake8 spade_bdi tests 55 | 56 | test: ## run tests quickly with the default Python 57 | py.test 58 | 59 | test-all: ## run tests on every Python version with tox 60 | tox 61 | 62 | coverage: ## check code coverage quickly with the default Python 63 | coverage run --source spade_bdi -m pytest 64 | coverage report -m 65 | coverage html 66 | $(BROWSER) htmlcov/index.html 67 | 68 | docs: ## generate Sphinx HTML documentation, including API docs 69 | rm -f docs/spade_bdi.rst 70 | rm -f docs/modules.rst 71 | sphinx-apidoc -o docs/ spade_bdi 72 | $(MAKE) -C docs clean 73 | $(MAKE) -C docs html 74 | $(BROWSER) docs/_build/html/index.html 75 | 76 | servedocs: docs ## compile the docs watching for changes 77 | watchmedo shell-command -p '*.rst' -c '$(MAKE) -C docs html' -R -D . 78 | 79 | release: dist ## package and upload a release 80 | twine upload dist/* 81 | 82 | dist: clean ## builds source and wheel package 83 | python setup.py sdist 84 | python setup.py bdist_wheel 85 | ls -l dist 86 | 87 | install: clean ## install the package to the active Python's site-packages 88 | python setup.py install 89 | -------------------------------------------------------------------------------- /docs/lists.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Managing Lists in AgentSpeak 3 | ============================ 4 | 5 | In AgentSpeak, lists are important data structures that enable agents to handle collections of items. While AgentSpeak does not offer the same list manipulation capabilities as imperative programming languages, it still provides ways to manage lists through pattern matching and recursion. This section explores how AgentSpeak handles lists. 6 | 7 | List Structure in AgentSpeak 8 | ============================ 9 | 10 | - **Representation**: Lists in AgentSpeak are represented as a collection of elements enclosed in brackets and separated by commas, e.g., ``[element1, element2, element3]``. 11 | - **Head and Tail**: Lists can be split into a "head" (the first element) and a "tail" (the remainder of the list). This is done using the pattern ``[Head|Tail]``. 12 | 13 | Basic Operations on Lists 14 | ========================= 15 | 16 | 1. **Accessing Elements**: 17 | - The first element of the list (head) and the rest (tail) can be accessed using list decomposition. 18 | - **Example**: 19 | :: 20 | 21 | +!process_list([Head|Tail]) : true <- 22 | .print("Processing", Head); 23 | !process_list(Tail). 24 | 25 | 2. **Adding Elements**: 26 | - AgentSpeak does not have a direct operation for adding elements, but this can be achieved by updating a list. 27 | - **Example**: 28 | :: 29 | 30 | +!add_element(Element) : list([List]) <- 31 | -+list([Element|List]). 32 | 33 | 34 | 3. **Removing Elements**: 35 | - Similar to adding elements, removing requires updating the list without the element to be removed. 36 | - **Example**:: 37 | 38 | +!remove_element(Element) : list([Element|Tail]) <- 39 | -+list([Tail]). 40 | 41 | 42 | Recursion in List Handling 43 | ========================== 44 | 45 | - **Recursive Processing**: To process lists, recursion is often used, where a plan calls itself with the list's "tail" until the list is empty. 46 | - **Example of Recursion**: 47 | :: 48 | 49 | +!process_list([Head|Tail]) : .length(Tail, X) & X > 0 <- 50 | .do_something_with(Head); 51 | !process_list(Tail). 52 | 53 | +!process_list([LastElement]) : true <- 54 | .do_something_with(LastElement). 55 | 56 | 57 | Managing lists in AgentSpeak, although not as straightforward as in other languages, is feasible and effective through list decomposition, creating new lists for adding or removing elements, and recursive patterns to process lists. These methods enable agents to dynamically handle sets of data and are essential for developing complex behaviors in multi-agent systems. 58 | 59 | -------------------------------------------------------------------------------- /docs/plans.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Creating Plans in AgentSpeak 3 | ============================ 4 | 5 | In AgentSpeak, plans are central to the behavior of agents. They define how an agent should react to certain events or changes in their environment or internal state. 6 | This section explores the syntax and structure of plans in AgentSpeak, providing examples and best practices. 7 | 8 | Plan Syntax 9 | =========== 10 | 11 | **Basic Structure**: A plan in AgentSpeak typically consists of a triggering event, an optional context, and a sequence of actions. The general format is:: 12 | 13 | TriggeringEvent : Context <- Actions. 14 | 15 | 16 | - Triggering Event: This is what initiates the plan. It can be the addition or removal of a belief (+belief or -belief), or the adoption or dropping of a goal (+!goal or -!goal). 17 | - Context: The context is a condition that must be true for the plan to be applicable. It's written as a logical expression. 18 | - Actions: These are the steps the agent will take, interacting with the environment or other agents. 19 | - Tag (Optional): Before the triggering event, a plan may have a tag beginning with a @ and followed by the name of the plan. 20 | 21 | Writing a Basic Plan 22 | ==================== 23 | 24 | Example: Suppose an agent needs to respond to a high temperature reading. 25 | The plan might look like this:: 26 | 27 | @refresh_plan 28 | +temperature(high) : is_outside <- 29 | !move_to_shade; 30 | !drink_water. 31 | 32 | In this plan, ``+temperature(high)`` is the triggering event (a belief that the temperature is high). 33 | The context ``is_outside`` checks if the agent is outside. The actions ``move_to_shade`` and ``drink_water`` are executed in sequence. 34 | 35 | 36 | Best Practices in Plan Creation 37 | =============================== 38 | 39 | When designing plans in AgentSpeak, it is important to consider the following best practices: 40 | 41 | - Modularity: Keep plans modular. Each plan should have a single, clear purpose. 42 | - Reusability: Design plans that can be reused in different situations. 43 | - Readability: Write clear and understandable plans, as AgentSpeak is a declarative language. 44 | 45 | Handling Failures in Plans 46 | ========================== 47 | 48 | Plans should account for potential failures. 49 | This can be done through alternative plans or by including failure-handling steps within the plan. 50 | Example with Failure Handling:: 51 | 52 | +!travel(destination) : car_is_functional <- 53 | drive(car, destination). 54 | +!travel(destination) : not car_is_functional <- 55 | call_taxi(destination). 56 | 57 | Here, there are two plans for the same goal ``!travel(destination)``. 58 | The first plan is used if the car is functional, and the second plan (calling a taxi) is a backup if the car isn't functional. -------------------------------------------------------------------------------- /examples/master_slave/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | from datetime import datetime, timedelta 5 | 6 | import spade 7 | from spade.behaviour import PeriodicBehaviour, TimeoutBehaviour 8 | from spade.template import Template 9 | 10 | from spade_bdi.bdi import BDIAgent 11 | 12 | 13 | class MasterAgent(BDIAgent): 14 | async def setup(self): 15 | template = Template(metadata={"performative": "Modify"}) 16 | self.add_behaviour(self.Modify(period=1, start_at=datetime.now()), template) 17 | 18 | template = Template(metadata={"performative": "Ending"}) 19 | self.add_behaviour(self.RemoveBeliefsBehav(start_at=datetime.now() + timedelta(seconds=5)), template) 20 | 21 | class Modify(PeriodicBehaviour): 22 | async def run(self): 23 | if self.agent.bdi_enabled: 24 | try: 25 | count_type = self.agent.bdi.get_belief_value("type")[0] 26 | if count_type == 'inc': 27 | self.agent.bdi.set_belief('type', 'dec') 28 | else: 29 | self.agent.bdi.set_belief('type', 'inc') 30 | except Exception as e: 31 | self.kill() 32 | 33 | class RemoveBeliefsBehav(TimeoutBehaviour): 34 | async def run(self): 35 | self.agent.bdi.remove_belief('type', 'inc') 36 | self.agent.bdi.remove_belief('type', 'dec') 37 | 38 | 39 | async def main(server, password): 40 | b = BDIAgent("slave_1@{}".format(server), password, "slave.asl") 41 | b.bdi.set_belief("master", "master@{}".format(server)) 42 | await b.start() 43 | 44 | c = BDIAgent("slave_2@{}".format(server), password, "slave.asl") 45 | c.pause_bdi() 46 | await c.start() 47 | 48 | a = MasterAgent("master@{}".format(server), password, "master.asl") 49 | a.bdi.set_belief("slave1", "slave_1@{}".format(server)) 50 | a.bdi.set_belief("slave2", "slave_2@{}".format(server)) 51 | a.bdi.set_belief('type', 'dec') 52 | await a.start() 53 | 54 | await asyncio.sleep(2) 55 | print("Enabling BDI for slave2") 56 | c.set_asl("slave.asl") 57 | c.bdi.set_belief("master", "master@{}".format(server)) 58 | await asyncio.sleep(4) 59 | print("Disabling BDI for slave2") 60 | c.pause_bdi() 61 | 62 | await a.stop() 63 | await b.stop() 64 | await c.stop() 65 | 66 | 67 | if __name__ == "__main__": 68 | parser = argparse.ArgumentParser() 69 | parser.add_argument("--server", help="XMPP Server") 70 | parser.add_argument("--password", help="Password") 71 | args = parser.parse_args() 72 | 73 | if args.server is None: 74 | server = input("XMPP Server> ") 75 | else: 76 | server = args.server 77 | 78 | if args.password is None: 79 | passwd = getpass.getpass() 80 | else: 81 | passwd = args.password 82 | spade.run(main(server, passwd)) 83 | -------------------------------------------------------------------------------- /examples/counter/run_example.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import asyncio 3 | import getpass 4 | from datetime import datetime, timedelta 5 | 6 | import spade 7 | from spade.behaviour import PeriodicBehaviour, TimeoutBehaviour 8 | from spade.template import Template 9 | 10 | from spade_bdi.bdi import BDIAgent 11 | 12 | 13 | class CounterAgent(BDIAgent): 14 | async def setup(self): 15 | template = Template(metadata={"performative": "B1"}) 16 | self.add_behaviour(self.UpdateCounterBehav(period=0.5, start_at=datetime.now()), template) 17 | template = Template(metadata={"performative": "B2"}) 18 | self.add_behaviour(self.ResetCounterBehav(period=2, start_at=datetime.now()), template) 19 | template = Template(metadata={"performative": "B3"}) 20 | self.add_behaviour(self.SwitchBeliefBehav(period=1, start_at=datetime.now()), template) 21 | template = Template(metadata={"performative": "B4"}) 22 | self.add_behaviour(self.RemoveBeliefsBehav(start_at=datetime.now() + timedelta(seconds=4.5)), template) 23 | 24 | class UpdateCounterBehav(PeriodicBehaviour): 25 | async def on_start(self): 26 | self.counter = self.agent.bdi.get_belief_value("counter")[0] 27 | 28 | async def run(self): 29 | if self.counter != self.agent.bdi.get_belief_value("counter")[0]: 30 | self.counter = self.agent.bdi.get_belief_value("counter")[0] 31 | print(self.agent.bdi.get_belief("counter")) 32 | 33 | class ResetCounterBehav(PeriodicBehaviour): 34 | async def run(self): 35 | self.agent.bdi.set_belief('counter', 0) 36 | 37 | class SwitchBeliefBehav(PeriodicBehaviour): 38 | async def run(self): 39 | try: 40 | type = self.agent.bdi.get_belief_value("type")[0] 41 | if type == 'inc': 42 | self.agent.bdi.set_belief('type', 'dec') 43 | else: 44 | self.agent.bdi.set_belief('type', 'inc') 45 | except Exception as e: 46 | print("No belief 'type'.") 47 | 48 | class RemoveBeliefsBehav(TimeoutBehaviour): 49 | async def run(self): 50 | self.agent.bdi.remove_belief('type', 'inc') 51 | self.agent.bdi.remove_belief('type', 'dec') 52 | 53 | 54 | async def main(server, password): 55 | a = CounterAgent("counter@" + server, password, "counter.asl") 56 | await a.start() 57 | 58 | await asyncio.sleep(5) 59 | await a.stop() 60 | 61 | 62 | if __name__ == "__main__": 63 | parser = argparse.ArgumentParser() 64 | parser.add_argument("--server", help="XMPP Server") 65 | parser.add_argument("--password", help="Password") 66 | args = parser.parse_args() 67 | 68 | if args.server is None: 69 | server = input("XMPP Server> ") 70 | else: 71 | server = args.server 72 | 73 | if args.password is None: 74 | passwd = getpass.getpass() 75 | else: 76 | passwd = args.password 77 | spade.run(main(server, passwd)) 78 | -------------------------------------------------------------------------------- /docs/communication.rst: -------------------------------------------------------------------------------- 1 | =========================== 2 | Communication in AgentSpeak 3 | =========================== 4 | 5 | Sending Messages 6 | ================ 7 | 8 | In AgentSpeak and multi-agent systems, communication is a key aspect of agent interaction. 9 | This section covers the process and considerations for sending messages between agents in AgentSpeak, with a focus on the syntax, types of messages, and practical implementation. 10 | 11 | Syntax for Sending Messages 12 | --------------------------- 13 | 14 | AgentSpeak provides a simple and flexible syntax for sending messages. The general form includes specifying the type of communicative act (ilocution), the recipient agent, and the content of the message. 15 | 16 | Basic Syntax:: 17 | 18 | .send(recipient, ilocution, content) 19 | 20 | where recipient is the identifier of the target agent, ilocution is the type of communicative act, and content is the message content. 21 | 22 | Types of Communicative acts: 23 | In AgentSpeak, communication between agents is achieved through illocutionary acts, often referred to as communicative acts. 24 | Unlike performatives, which are more general in speech act theory, AgentSpeak uses specific types of illocutions to facilitate clear and purpose-driven agent interactions. 25 | Here are the key illocutions used in AgentSpeak: 26 | 27 | - ``tell``: Used to inform another agent about a belief. This act is about sharing knowledge or facts. For example, an agent might tell another agent that a specific condition is true:: 28 | 29 | .send(agentB, tell, weather(raining)); 30 | 31 | - ``achieve``: Sent to request another agent to perform some action or bring about a certain state of affairs. This is similar to a request or command in conventional communication:: 32 | 33 | .send(agentB, achieve, fix_the_leak); 34 | 35 | - ``tellHow``: This illocution is used when an agent wants to inform another agent about how to perform a specific action or achieve a goal. It's about sharing procedural knowledge:: 36 | 37 | .send(agentB, tellHow, "+!solve_problem <- !gather_data; !analyze_data."); 38 | 39 | - ``askHow``: When an agent needs to know how to perform an action or achieve a goal, it uses askHow to request this procedural knowledge from another agent.:: 40 | 41 | .send(agentB, askHow, learn_chess); 42 | 43 | - ``untell``: This is used to inform another agent that a previously held belief is no longer true. It's a way of updating or correcting earlier information:: 44 | 45 | .send(agentB, untell, weather(raining)); 46 | 47 | - ``unachieve``: Sent to request that another agent cease its efforts to achieve a previously requested goal. It's like a cancellation or retraction of a previous achieve request:: 48 | 49 | .send(agentB, unachieve, fix_the_leak); 50 | 51 | - ``untellHow``: Used to inform another agent to disregard previously told procedural knowledge. This might be used if the procedure is no longer valid or has been updated:: 52 | 53 | .send(agentB, untellHow, "@plan_name"); 54 | 55 | Each of these illocutions plays a vital role in the communication protocol within a multi-agent system, allowing agents to share knowledge, coordinate actions, and update each other on changes in beliefs or plans. When designing AgentSpeak agents, it is crucial to implement these illocutions correctly to ensure effective and coherent agent interactions. 56 | 57 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Spade-BDI 3 | ========= 4 | 5 | .. image:: https://img.shields.io/pypi/v/spade_bdi.svg 6 | :target: https://pypi.python.org/pypi/spade 7 | 8 | .. image:: https://img.shields.io/pypi/pyversions/spade_bdi.svg 9 | :target: https://pypi.python.org/pypi/spade_bdi 10 | 11 | .. image:: https://img.shields.io/pypi/l/spade_bdi 12 | :target: https://opensource.org/licenses/MIT 13 | :alt: MIT License 14 | 15 | .. image:: https://pepy.tech/badge/spade_bdi 16 | :target: https://pepy.tech/project/spade_bdi 17 | :alt: Downloads 18 | 19 | .. image:: https://readthedocs.org/projects/spade_bdi/badge/?version=latest 20 | :target: https://spade-bdi.readthedocs.io?badge=latest 21 | :alt: Documentation Status 22 | 23 | .. image:: https://img.shields.io/pypi/format/spade_bdi.svg 24 | :target: https://pypi.python.org/pypi/spade_bdi 25 | 26 | 27 | Create hybrid agents with a BDI layer for the SPADE MAS Platform. 28 | 29 | 30 | * Free software: MIT License 31 | * Documentation: https://spade-bdi.readthedocs.io. 32 | 33 | 34 | Features 35 | -------- 36 | 37 | * Create agents that parse and execute an ASL file written in AgentSpeak. 38 | * Supports Agentspeak-like BDI behaviours. 39 | * Add custom actions and functions. 40 | * Send TELL, UNTELL and ACHIEVE KQML performatives. 41 | 42 | Examples 43 | -------- 44 | 45 | basic.py:: 46 | 47 | import getpass 48 | from spade_bdi.bdi import BDIAgent 49 | 50 | server = input("Please enter the XMPP server address: ") 51 | password = getpass.getpass("Please enter the password: ") 52 | 53 | a = BDIAgent("BasicAgent@" + server, password, "basic.asl") 54 | a.start() 55 | 56 | a.bdi.set_belief("car", "blue", "big") 57 | a.bdi.print_beliefs() 58 | 59 | print(a.bdi.get_belief("car")) 60 | a.bdi.print_beliefs() 61 | 62 | a.bdi.remove_belief("car", 'blue', "big") 63 | a.bdi.print_beliefs() 64 | 65 | print(a.bdi.get_beliefs()) 66 | a.bdi.set_belief("car", 'yellow') 67 | 68 | 69 | basic.asl:: 70 | 71 | !start. 72 | 73 | +!start <- 74 | +car(red); 75 | .a_function(3,W); 76 | .print("w =", W); 77 | literal_function(red,Y); 78 | .print("Y =", Y); 79 | .custom_action(8); 80 | +truck(blue). 81 | 82 | +car(Color) 83 | <- .print("The car is ",Color). 84 | 85 | 86 | Examples 87 | -------- 88 | 89 | basic.py:: 90 | 91 | import getpass 92 | from spade_bdi.bdi import BDIAgent 93 | 94 | server = input("Please enter the XMPP server address: ") 95 | password = getpass.getpass("Please enter the password: ") 96 | 97 | a = BDIAgent("BasicAgent@" + server, password, "basic.asl") 98 | a.start() 99 | 100 | a.bdi.set_belief("car", "blue", "big") 101 | a.bdi.print_beliefs() 102 | 103 | print(a.bdi.get_belief("car")) 104 | a.bdi.print_beliefs() 105 | 106 | a.bdi.remove_belief("car", 'blue', "big") 107 | a.bdi.print_beliefs() 108 | 109 | print(a.bdi.get_beliefs()) 110 | a.bdi.set_belief("car", 'yellow') 111 | 112 | 113 | basic.asl:: 114 | 115 | !start. 116 | 117 | +!start <- 118 | +car(red); 119 | .a_function(3,W); 120 | .print("w =", W); 121 | literal_function(red,Y); 122 | .print("Y =", Y); 123 | .custom_action(8); 124 | +truck(blue). 125 | 126 | +car(Color) 127 | <- .print("The car is ",Color). 128 | 129 | 130 | Credits 131 | ------- 132 | 133 | This package was created with Cookiecutter_ and the `audreyr/cookiecutter-pypackage`_ project template. 134 | 135 | .. _Cookiecutter: https://github.com/audreyr/cookiecutter 136 | .. _`audreyr/cookiecutter-pypackage`: https://github.com/audreyr/cookiecutter-pypackage 137 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: shell 2 | 3 | ============ 4 | Contributing 5 | ============ 6 | 7 | Contributions are welcome, and they are greatly appreciated! Every little bit 8 | helps, and credit will always be given. 9 | 10 | You can contribute in many ways: 11 | 12 | Types of Contributions 13 | ---------------------- 14 | 15 | Report Bugs 16 | ~~~~~~~~~~~ 17 | 18 | Report bugs at https://github.com/javipalanca/spade_bdi/issues. 19 | 20 | If you are reporting a bug, please include: 21 | 22 | * Your operating system name and version. 23 | * Any details about your local setup that might be helpful in troubleshooting. 24 | * Detailed steps to reproduce the bug. 25 | 26 | Fix Bugs 27 | ~~~~~~~~ 28 | 29 | Look through the GitHub issues for bugs. Anything tagged with "bug" and "help 30 | wanted" is open to whoever wants to implement it. 31 | 32 | Implement Features 33 | ~~~~~~~~~~~~~~~~~~ 34 | 35 | Look through the GitHub issues for features. Anything tagged with "enhancement" 36 | and "help wanted" is open to whoever wants to implement it. 37 | 38 | Write Documentation 39 | ~~~~~~~~~~~~~~~~~~~ 40 | 41 | Spade-BDI could always use more documentation, whether as part of the 42 | official Spade-BDI docs, in docstrings, or even on the web in blog posts, 43 | articles, and such. 44 | 45 | Submit Feedback 46 | ~~~~~~~~~~~~~~~ 47 | 48 | The best way to send feedback is to file an issue at https://github.com/javipalanca/spade_bdi/issues. 49 | 50 | If you are proposing a feature: 51 | 52 | * Explain in detail how it would work. 53 | * Keep the scope as narrow as possible, to make it easier to implement. 54 | * Remember that this is a volunteer-driven project, and that contributions 55 | are welcome :) 56 | 57 | Get Started! 58 | ------------ 59 | 60 | Ready to contribute? Here's how to set up `spade_bdi` for local development. 61 | 62 | 1. Fork the `spade_bdi` repo on GitHub. 63 | 2. Clone your fork locally:: 64 | 65 | $ git clone git@github.com:your_name_here/spade_bdi.git 66 | 67 | 3. Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:: 68 | 69 | $ mkvirtualenv spade_bdi 70 | $ cd spade_bdi/ 71 | $ python setup.py develop 72 | 73 | 4. Create a branch for local development:: 74 | 75 | $ git checkout -b name-of-your-bugfix-or-feature 76 | 77 | Now you can make your changes locally. 78 | 79 | 5. When you're done making changes, check that your changes pass flake8 and the 80 | tests, including testing other Python versions with tox:: 81 | 82 | $ flake8 spade_bdi tests 83 | $ python setup.py test or py.test 84 | $ tox 85 | 86 | To get flake8 and tox, just pip install them into your virtualenv. 87 | 88 | 6. Commit your changes and push your branch to GitHub:: 89 | 90 | $ git add . 91 | $ git commit -m "Your detailed description of your changes." 92 | $ git push origin name-of-your-bugfix-or-feature 93 | 94 | 7. Submit a pull request through the GitHub website. 95 | 96 | Pull Request Guidelines 97 | ----------------------- 98 | 99 | Before you submit a pull request, check that it meets these guidelines: 100 | 101 | 1. The pull request should include tests. 102 | 2. If the pull request adds functionality, the docs should be updated. Put 103 | your new functionality into a function with a docstring, and add the 104 | feature to the list in README.rst. 105 | 3. The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check 106 | https://travis-ci.org/javipalanca/spade_bdi/pull_requests 107 | and make sure that the tests pass for all supported Python versions. 108 | 109 | Tips 110 | ---- 111 | 112 | To run a subset of tests:: 113 | 114 | $ py.test tests.test_spade_bdi 115 | 116 | 117 | Deploying 118 | --------- 119 | 120 | A reminder for the maintainers on how to deploy. 121 | Make sure all your changes are committed (including an entry in HISTORY.rst). 122 | Then run:: 123 | 124 | $ bumpversion patch # possible: major / minor / patch 125 | $ git push 126 | $ git push --tags 127 | 128 | Travis will then deploy to PyPI if tests pass. 129 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # spade_bdi documentation build configuration file, created by 5 | # sphinx-quickstart on Fri Jun 9 13:47:02 2017. 6 | # 7 | # This file is execfile()d with the current directory set to its 8 | # containing dir. 9 | # 10 | # Note that not all possible configuration values are present in this 11 | # autogenerated file. 12 | # 13 | # All configuration values have a default; values that are commented out 14 | # serve to show the default. 15 | 16 | # If extensions (or modules to document with autodoc) are in another 17 | # directory, add these directories to sys.path here. If the directory is 18 | # relative to the documentation root, use os.path.abspath to make it 19 | # absolute, like shown here. 20 | # 21 | import os 22 | import sys 23 | sys.path.insert(0, os.path.abspath('..')) 24 | 25 | import spade_bdi 26 | 27 | # -- General configuration --------------------------------------------- 28 | 29 | # If your documentation needs a minimal Sphinx version, state it here. 30 | # 31 | # needs_sphinx = '1.0' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 35 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.autosectionlabel'] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # The suffix(es) of source filenames. 41 | # You can specify multiple suffix as a list of string: 42 | # 43 | # source_suffix = ['.rst', '.md'] 44 | source_suffix = '.rst' 45 | 46 | # The master toctree document. 47 | master_doc = 'index' 48 | 49 | # General information about the project. 50 | project = u'Spade-BDI' 51 | copyright = u"2019, Sergio Frayle Pérez" 52 | author = u"Sergio Frayle Pérez" 53 | 54 | # The version info for the project you're documenting, acts as replacement 55 | # for |version| and |release|, also used in various other places throughout 56 | # the built documents. 57 | # 58 | # The short X.Y version. 59 | version = spade_bdi.__version__ 60 | # The full version, including alpha/beta/rc tags. 61 | release = spade_bdi.__version__ 62 | 63 | # The language for content autogenerated by Sphinx. Refer to documentation 64 | # for a list of supported languages. 65 | # 66 | # This is also used if you do content translation via gettext catalogs. 67 | # Usually you set "language" from the command line for these cases. 68 | language = None 69 | 70 | # List of patterns, relative to source directory, that match files and 71 | # directories to ignore when looking for source files. 72 | # This patterns also effect to html_static_path and html_extra_path 73 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 74 | 75 | # The name of the Pygments (syntax highlighting) style to use. 76 | pygments_style = 'sphinx' 77 | 78 | # If true, `todo` and `todoList` produce output, else they produce nothing. 79 | todo_include_todos = False 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'sphinx_rtd_theme' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a 90 | # theme further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | 101 | # -- Options for HTMLHelp output --------------------------------------- 102 | 103 | # Output file base name for HTML help builder. 104 | htmlhelp_basename = 'spade_bdidoc' 105 | 106 | 107 | # -- Options for LaTeX output ------------------------------------------ 108 | 109 | latex_elements = { 110 | # The paper size ('letterpaper' or 'a4paper'). 111 | # 112 | # 'papersize': 'letterpaper', 113 | 114 | # The font size ('10pt', '11pt' or '12pt'). 115 | # 116 | # 'pointsize': '10pt', 117 | 118 | # Additional stuff for the LaTeX preamble. 119 | # 120 | # 'preamble': '', 121 | 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, author, documentclass 129 | # [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, 'spade_bdi.tex', 132 | u'Spade-BDI Documentation', 133 | u'Sergio Frayle Pérez', 'manual'), 134 | ] 135 | 136 | 137 | # -- Options for manual page output ------------------------------------ 138 | 139 | # One entry per manual page. List of tuples 140 | # (source start file, name, description, authors, manual section). 141 | man_pages = [ 142 | (master_doc, 'spade_bdi', 143 | u'Spade-BDI Documentation', 144 | [author], 1) 145 | ] 146 | 147 | 148 | # -- Options for Texinfo output ---------------------------------------- 149 | 150 | # Grouping the document tree into Texinfo files. List of tuples 151 | # (source start file, target name, title, author, 152 | # dir menu entry, description, category) 153 | texinfo_documents = [ 154 | (master_doc, 'spade_bdi', 155 | u'Spade-BDI Documentation', 156 | author, 157 | 'spade_bdi', 158 | 'One line description of project.', 159 | 'Miscellaneous'), 160 | ] 161 | 162 | 163 | 164 | -------------------------------------------------------------------------------- /docs/agentspeak.rst: -------------------------------------------------------------------------------- 1 | ======================= 2 | The AgentSpeak language 3 | ======================= 4 | 5 | The AgentSpeak language is a logic programming language based on the Belief-Desire-Intention (BDI) model. 6 | It is based on the ``agentspeak`` package, which is a Python implementation of the Jason language. 7 | The language is described in the following paper: 8 | 9 | ``Rao, A. S., & Georgeff, M. P. (1995). BDI agents: From theory to practice. ICMAS.`` 10 | ``https://cdn.aaai.org/ICMAS/1995/ICMAS95-042.pdf_`` 11 | 12 | 13 | This section provides an overview of its syntax and semantics, focusing on how beliefs, desires, and goals are 14 | represented and managed in AgentSpeak. 15 | 16 | Basic Semantics 17 | =============== 18 | 19 | - **Beliefs**: In AgentSpeak, beliefs represent the agent's knowledge about the world, itself, and other agents. They are often expressed in a simple predicate form. For example, ``is_hot(temperature)`` might represent the belief that the temperature is hot. 20 | - **Desires and Goals**: Desires or goals are states or conditions that the agent aims to bring about. In AgentSpeak, these are often represented as special kinds of beliefs or through goal operators. For instance, ``!find_shade`` could be a goal to find shade. 21 | - **Plans and Actions**: Plans are sequences of actions or steps that an agent will execute to achieve its goals. Actions can be internal (changing beliefs or goals) or external (interacting with the environment). 22 | 23 | Syntax of AgentSpeak 24 | ==================== 25 | 26 | AgentSpeak is a logic-based programming language used for creating intelligent agents in multi-agent systems. Understanding its syntax is crucial for effectively programming these agents. This section provides an overview of the key syntactic elements of AgentSpeak. 27 | 28 | Basic Elements 29 | 30 | - **Beliefs:** 31 | - Syntax: ``belief(arguments)``. 32 | - Description: Represent the agent's knowledge or information about the world. 33 | - Example: ``is_sunny(true), temperature(25)``. 34 | - **Goals:** 35 | - Syntax: ``!goal(arguments)``. 36 | - Description: Goals are states or outcomes the agent wants to bring about or information it seeks. 37 | - Example: ``!find_shelter``. 38 | - **Plans:** 39 | - Syntax: ``TriggeringEvent : Context <- Body.`` 40 | - Triggering Event: An event that initiates the plan, such as the addition (+) or deletion (-) of a belief or goal. 41 | - Context: A logical condition that must hold for the plan to be applicable. 42 | - Body: A sequence of actions or subgoals to be executed. 43 | - Example: ``+is_raining : is_outside <- !find_umbrella; .print("Hello world").`` 44 | - **Actions** 45 | - Syntax: ``.internal_action(arguments)``. 46 | - Description: Defined by the developer or the environment. 47 | - Example: ``.print("Hello World")``. 48 | 49 | - **Communication** 50 | - Sending Messages: 51 | - Syntax: ``.send(receiver, illocution, content)``. 52 | - Illocutions: Include tell, achieve, askHow, etc. 53 | - Example: ``.send(agentB, tell, is_sunny(true))``. 54 | 55 | - **Comments** 56 | - Single Line Comment: // This is a comment 57 | - Multi-Line Comment: Not typically supported in standard AgentSpeak. 58 | 59 | 60 | Creating Agents: Beliefs, Desires, and Goals 61 | ============================================ 62 | 63 | Agents are defined by their belief base, goal base, and plan library. 64 | 65 | - Example of Beliefs:: 66 | 67 | is_sunny. 68 | temperature(high). 69 | 70 | 71 | This represents beliefs that it is sunny and the temperature is high. 72 | 73 | - Example of Goals:: 74 | 75 | !stay_cool. 76 | !drink_water. 77 | 78 | 79 | These are goals to stay cool and to drink water. 80 | 81 | - Plans and Actions 82 | 83 | A plan in AgentSpeak is a rule that specifies what to do in a given context. 84 | Example of a Plan:: 85 | 86 | 87 | +!stay_cool : is_sunny & temperature(high) <- 88 | !find_shade; 89 | !drink_water. 90 | 91 | 92 | This plan states that to achieve the goal ``stay_cool``, if it is sunny and the temperature is high 93 | (``is_sunny & temperature(high)``), the agent should achive goals ``!find_shade`` and ``!drink_water`` sequentially. 94 | 95 | Optionally, a plan may have a custom a name that is set with a tag beginning with a @. Example:: 96 | 97 | @my_custom_plan 98 | +!stay_cool : is_sunny & temperature(high) <- 99 | !find_shade; 100 | !drink_water. 101 | 102 | Practical Implications 103 | ====================== 104 | 105 | Understanding these basic concepts is crucial for effectively programming in AgentSpeak. 106 | ``spade_bdi`` provides additional constructs and features, enhancing the basic capabilities of AgentSpeak. 107 | When designing agents in SPADE, it is essential to carefully consider the initial set of beliefs and goals, as they drive the agent's behavior through the plans. 108 | By grasping these fundamental concepts of AgentSpeak, developers can begin to design and implement sophisticated agents in SPADE, capable of complex decision-making and interactions in dynamic environments. 109 | The simplicity of AgentSpeak's syntax, combined with its powerful representational capabilities, makes it a suitable choice for a wide range of applications in multi-agent systems. 110 | 111 | 112 | Variables and the '?' Operator in AgentSpeak 113 | -------------------------------------------- 114 | 115 | In AgentSpeak, variables are essential for dynamic information processing within an agent's logic. 116 | They are uniquely identified by starting with an uppercase letter, distinguishing them from constants and predicates. This section delves into the syntax and use of variables, focusing on the ``?`` operator for retrieving belief values. 117 | 118 | 119 | Syntax of Variables in AgentSpeak 120 | ================================= 121 | 122 | **Uppercase Naming**: Variables in AgentSpeak are always denoted by names starting with an uppercase letter. This convention distinguishes them from other elements like predicates or constants. 123 | Example of Variable Declaration: ``Location, Temp, X, Y`` 124 | 125 | Using the ``'?'`` Operator to Retrieve Belief Values 126 | ---------------------------------------------------- 127 | 128 | - **Purpose**: The ``?`` operator in AgentSpeak is used to bind the current value of a belief to a variable. This operation is akin to querying the agent's belief base. 129 | - **Syntax**: To use the ``?`` operator, include it before the belief name and specify the variable in the belief's argument list. The format is typically ``?Belief(Variable)``. 130 | - **Example**: If an agent has a belief ``location(office)``, and you want to bind the value office to a variable ``CurrentLocation``, you would use the statement ``?location(CurrentLocation)``. 131 | 132 | Practical Application of Variables 133 | ================================== 134 | 135 | * Retrieving and Using Belief Values: 136 | 137 | Variables are particularly useful for capturing and utilizing the values of beliefs in plans and decision-making. Example:: 138 | 139 | +!check_current_location 140 | : location(CurrentLocation) & CurrentLocation == "office" <- 141 | .print("The agent is currently in the office"). 142 | 143 | 144 | Here, ``CurrentLocation`` is a variable that retrieves the value from the location belief. 145 | 146 | * Dynamic Decision-Making in Contexts: 147 | 148 | Variables enable plans to adapt their behavior based on the changing state of the world, as represented by the agent's beliefs. Example:: 149 | 150 | +temperature(Temp) : Temp > 30 <- 151 | .print("It's currently hot outside"). 152 | 153 | In this example, Temp is a variable that holds the current value of the temperature belief, triggering the plan if Temp exceeds 30. 154 | 155 | Conclusion 156 | ---------- 157 | 158 | Proper use of variables and the ``?`` operator in AgentSpeak is fundamental for creating dynamic and responsive agents. 159 | Variables, identified by their uppercase starting letter, offer a way to handle changing information and make context-sensitive decisions. 160 | The ``?`` operator is a key tool for querying and utilizing the agent's belief base, enhancing the agent's ability to interact intelligently with its environment. 161 | -------------------------------------------------------------------------------- /spade_bdi/bdi.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import asyncio 3 | import collections 4 | import time 5 | from ast import literal_eval 6 | from collections import deque 7 | 8 | import agentspeak as asp 9 | from agentspeak import runtime as asp_runtime, stdlib as asp_stdlib 10 | from agentspeak.runtime import plan_to_str 11 | from loguru import logger 12 | from spade.agent import Agent 13 | from spade.behaviour import CyclicBehaviour 14 | from spade.message import Message 15 | from spade.template import Template 16 | 17 | import re 18 | 19 | 20 | PERCEPT_TAG = frozenset([asp.Literal("source", (asp.Literal("percept"),))]) 21 | 22 | 23 | class BeliefNotInitiated(Exception): 24 | pass 25 | 26 | 27 | class BDIAgent(Agent): 28 | def __init__( 29 | self, jid: str, password: str, asl: str, actions=None, *args, **kwargs 30 | ): 31 | self.asl_file = asl 32 | self.bdi_enabled = False 33 | self.bdi_intention_buffer = deque() 34 | self.bdi = None 35 | self.bdi_agent = None 36 | 37 | super().__init__(jid, password, *args, **kwargs) 38 | while not self.loop: 39 | time.sleep(0.01) 40 | 41 | template = Template(metadata={"performative": "BDI"}) 42 | self.add_behaviour(self.BDIBehaviour(), template) 43 | 44 | self.bdi_env = asp_runtime.Environment() 45 | self.bdi_actions = asp.Actions(asp_stdlib.actions) if not actions else actions 46 | self.bdi.add_actions() 47 | self.add_custom_actions(self.bdi_actions) 48 | self._load_asl() 49 | 50 | def add_custom_actions(self, actions): 51 | pass 52 | 53 | def pause_bdi(self): 54 | self.bdi_enabled = False 55 | 56 | def resume_bdi(self): 57 | self.bdi_enabled = True 58 | 59 | def add_behaviour(self, behaviour, template=None): 60 | if type(behaviour) == self.BDIBehaviour: 61 | self.bdi = behaviour 62 | super().add_behaviour(behaviour, template) 63 | 64 | def set_asl(self, asl_file: str): 65 | self.asl_file = asl_file 66 | self._load_asl() 67 | 68 | def _load_asl(self): 69 | self.pause_bdi() 70 | try: 71 | with open(self.asl_file) as source: 72 | self.bdi_agent = self.bdi_env.build_agent(source, self.bdi_actions) 73 | self.bdi_agent.name = self.jid 74 | self.resume_bdi() 75 | except FileNotFoundError: 76 | logger.info( 77 | "Warning: ASL specified for {} does not exist. Disabling BDI.".format( 78 | self.jid 79 | ) 80 | ) 81 | self.asl_file = None 82 | self.pause_bdi() 83 | 84 | class BDIBehaviour(CyclicBehaviour): 85 | def add_actions(self): 86 | @self.agent.bdi_actions.add(".send", 3) 87 | def _send(agent, term, intention): 88 | receivers = asp.grounded(term.args[0], intention.scope) 89 | if isinstance(receivers, str) or isinstance(receivers, asp.Literal): 90 | receivers = (receivers,) 91 | ilf = asp.grounded(term.args[1], intention.scope) 92 | if not asp.is_atom(ilf): 93 | return 94 | ilf_type = ilf.functor 95 | mdata = { 96 | "performative": "BDI", 97 | "ilf_type": ilf_type, 98 | } 99 | for receiver in receivers: 100 | body = asp.asl_str(asp.freeze(term.args[2], intention.scope, {})) 101 | msg = Message(to=str(receiver), body=body, metadata=mdata) 102 | self.agent.submit(self.send(msg)) 103 | yield 104 | 105 | def set_belief(self, name: str, *args): 106 | """Set an agent's belief. If it already exists, updates it.""" 107 | new_args = () 108 | for x in args: 109 | if type(x) == str: 110 | new_args += (asp.Literal(x),) 111 | else: 112 | new_args += (x,) 113 | term = asp.Literal(name, tuple(new_args), PERCEPT_TAG) 114 | found = False 115 | for belief in list(self.agent.bdi_agent.beliefs[term.literal_group()]): 116 | if asp.unifies(term, belief): 117 | found = True 118 | else: 119 | self.agent.bdi_intention_buffer.append( 120 | ( 121 | asp.Trigger.removal, 122 | asp.GoalType.belief, 123 | belief, 124 | asp.runtime.Intention(), 125 | ) 126 | ) 127 | if not found: 128 | self.agent.bdi_intention_buffer.append( 129 | ( 130 | asp.Trigger.addition, 131 | asp.GoalType.belief, 132 | term, 133 | asp.runtime.Intention(), 134 | ) 135 | ) 136 | 137 | def remove_belief(self, name: str, *args): 138 | """Remove an existing agent's belief.""" 139 | new_args = () 140 | for x in args: 141 | if type(x) == str: 142 | new_args += (asp.Literal(x),) 143 | else: 144 | new_args += (x,) 145 | term = asp.Literal(name, tuple(new_args), PERCEPT_TAG) 146 | self.agent.bdi_intention_buffer.append( 147 | ( 148 | asp.Trigger.removal, 149 | asp.GoalType.belief, 150 | term, 151 | asp.runtime.Intention(), 152 | ) 153 | ) 154 | 155 | def get_belief(self, key: str, source=False): 156 | """Get an agent's existing belief. The first belief matching 157 | is returned. Keep False to strip source.""" 158 | key = str(key) 159 | for beliefs in self.agent.bdi_agent.beliefs: 160 | if beliefs[0] == key: 161 | if len(self.agent.bdi_agent.beliefs[beliefs]) == 0: 162 | raise BeliefNotInitiated(key) 163 | raw_belief = str(list(self.agent.bdi_agent.beliefs[beliefs])[0]) 164 | raw_belief = self._remove_source(raw_belief, source) 165 | belief = raw_belief 166 | return belief 167 | return None 168 | 169 | @staticmethod 170 | def _remove_source(belief, source): 171 | if ")[source" in belief and not source: 172 | belief = belief.split("[")[0].replace('"', "") 173 | return belief 174 | 175 | def get_belief_value(self, key: str): 176 | """Get an agent's existing value or values of the belief. The first belief matching 177 | is returned""" 178 | belief = self.get_belief(key) 179 | if belief: 180 | return tuple(belief.split("(")[1].split(")")[0].split(",")) 181 | else: 182 | return None 183 | 184 | def get_beliefs(self, source=False): 185 | """Get agent's beliefs.Keep False to strip source.""" 186 | belief_list = [] 187 | for beliefs in self.agent.bdi_agent.beliefs: 188 | try: 189 | raw_belief = str(list(self.agent.bdi_agent.beliefs[beliefs])[0]) 190 | raw_belief = self._remove_source(raw_belief, source) 191 | belief_list.append(raw_belief) 192 | except IndexError: 193 | pass 194 | return belief_list 195 | 196 | def print_beliefs(self, source=False): 197 | """Print agent's beliefs.Keep False to strip source.""" 198 | for beliefs in self.agent.bdi_agent.beliefs.values(): 199 | for belief in beliefs: 200 | print(self._remove_source(str(belief), source)) 201 | 202 | async def run(self): 203 | """ 204 | Coroutine run cyclic. 205 | """ 206 | if self.agent.bdi_enabled: 207 | msg = await self.receive(timeout=0) 208 | if msg: 209 | mdata = msg.metadata 210 | ilf_type = mdata["ilf_type"] 211 | if ilf_type == "tell": 212 | goal_type = asp.GoalType.belief 213 | trigger = asp.Trigger.addition 214 | elif ilf_type == "untell": 215 | goal_type = asp.GoalType.belief 216 | trigger = asp.Trigger.removal 217 | elif ilf_type == "achieve": 218 | goal_type = asp.GoalType.achievement 219 | trigger = asp.Trigger.addition 220 | elif ilf_type == "unachieve": 221 | goal_type = asp.GoalType.achievement 222 | trigger = asp.Trigger.removal 223 | elif ilf_type == "tellHow": 224 | goal_type = asp.GoalType.tellHow 225 | trigger = asp.Trigger.addition 226 | elif ilf_type == "untellHow": 227 | goal_type = asp.GoalType.tellHow 228 | trigger = asp.Trigger.removal 229 | elif ilf_type == "askHow": 230 | goal_type = asp.GoalType.askHow 231 | trigger = asp.Trigger.addition 232 | else: 233 | raise asp.AslError( 234 | "unknown illocutionary force: {}".format(ilf_type) 235 | ) 236 | 237 | intention = asp.runtime.Intention() 238 | 239 | # Prepare message. The message is either a plain text or a structured message. 240 | if ilf_type in ["tellHow", "untellHow"]: 241 | message = asp.Literal("plain_text", (msg.body,), frozenset()) 242 | elif ilf_type == "askHow": 243 | message = asp.Literal("plain_text", (msg.body,), frozenset()) 244 | 245 | def _call_ask_how(self, receiver, message, intention): 246 | # message.args[0] is the string plan to be sent 247 | body = asp.asl_str( 248 | asp.freeze(message.args[0], intention.scope, {}) 249 | ) 250 | mdata = { 251 | "performative": "BDI", 252 | "ilf_type": "tellHow", 253 | } 254 | msg = Message(to=receiver, body=body, metadata=mdata) 255 | _call_ask_how.spade_agent.submit( 256 | _call_ask_how.spade_class.send(msg) 257 | ) 258 | 259 | _call_ask_how.spade_agent = self.agent 260 | 261 | _call_ask_how.spade_class = self 262 | 263 | asp_runtime.Agent._call_ask_how = _call_ask_how 264 | 265 | # Overrides function ask_how from module agentspeak 266 | asp_runtime.Agent._ask_how = _ask_how 267 | 268 | else: 269 | # Sends a literal 270 | functor, args = parse_literal(msg.body) 271 | 272 | message = asp.Literal(functor, args) 273 | 274 | message = asp.freeze(message, intention.scope, {}) 275 | 276 | # Add source to message 277 | tagged_message = message.with_annotation( 278 | asp.Literal("source", (asp.Literal(str(msg.sender)),)) 279 | ) 280 | if ilf_type == "tellHow": 281 | pass 282 | 283 | self.agent.bdi_intention_buffer.append( 284 | (trigger, goal_type, tagged_message, intention) 285 | ) 286 | 287 | if self.agent.bdi_intention_buffer: 288 | temp_intentions = deque(self.agent.bdi_intention_buffer) 289 | for trigger, goal_type, term, intention in temp_intentions: 290 | self.agent.bdi_agent.call(trigger, goal_type, term, intention) 291 | self.agent.bdi_intention_buffer.popleft() 292 | 293 | self.agent.bdi_agent.step() 294 | 295 | else: 296 | await asyncio.sleep(0.1) 297 | 298 | 299 | def parse_literal(msg): 300 | functor = msg.split("(")[0] 301 | 302 | if "(" in msg: 303 | args = msg.split("(")[1] 304 | args = args.split(")")[0] 305 | 306 | x = re.search("^_X_*", args) 307 | 308 | if x is not None: 309 | args = asp.Var() 310 | else: 311 | args = literal_eval(args) 312 | 313 | def recursion(arg): 314 | if isinstance(arg, list): 315 | return tuple(recursion(i) for i in arg) 316 | return arg 317 | 318 | new_args = (recursion(args),) 319 | 320 | else: 321 | new_args = "" 322 | return functor, new_args 323 | 324 | 325 | def _ask_how(self, term): 326 | """ 327 | AskHow is a performative that allows the agent to ask for a plan to another agent. 328 | We look in the plan.list of the slave agent the plan that master want, 329 | if we find it: master agent use tellHow to tell the plan to slave agent 330 | """ 331 | sender_name = None 332 | 333 | # Receive the agent that ask for the plan 334 | for annotation in list(term.annots): 335 | if annotation.functor == "source": 336 | sender_name = annotation.args[0].functor 337 | 338 | if sender_name is None: 339 | raise asp.AslError("expected source annotation") 340 | 341 | plans_wanted = collections.defaultdict(lambda: []) 342 | plans = self.plans.values() 343 | 344 | # Find the plans 345 | for plan in plans: 346 | for differents in plan: 347 | if differents.head.functor in term.args[0]: 348 | plans_wanted[ 349 | ( 350 | differents.trigger, 351 | differents.goal_type, 352 | differents.head.functor, 353 | len(differents.head.args), 354 | ) 355 | ].append(differents) 356 | 357 | for plan in plans_wanted.values(): 358 | for different in plan: 359 | strplan = plan_to_str(different) 360 | message = asp.Literal("plain_text", (strplan,), frozenset()) 361 | tagged_message = message.with_annotation( 362 | asp.Literal("source", (asp.Literal(sender_name),)) 363 | ) 364 | self._call_ask_how(sender_name, message, asp.runtime.Intention()) 365 | --------------------------------------------------------------------------------