Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Test task "Project A" #20

Open
wants to merge 17 commits into
base: fix/demo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,51 @@ docker container run -it valory/open-autonomy-user:latest

Check out the `Makefile` for useful commands, e.g. `make formatters`, `make generators`, `make code-checks`, as well
as `make common-checks-1`. To run tests use the `autonomy test` command. Run `autonomy test --help` for help about its usage.


## Python Software Engineer - Project A

### Instructions

Your task, should you choose to accept it, is to implement an autonomous agent.
Autonomous agents have a number of defining characteristics:
- Communicate with the environment via asynchronous messages
- Display reactiveness (handling messages) and proactiveness (generating new
messages based on internal state or local time)
- Can be thought of as representing a human, organisation, or thing in a specific
domain and tasks

Your autonomous agent should support these operations and characteristics:
- Continuously consume messages (of different types) from an InBox
- Emit messages to an OutBox
- Allow for registration of message handlers to handle a given message type with its
specific handler (reactive: if this message then that is done)
- Allow for registration of behaviours (proactive: if this internal state or local time is
reached then this message is created)

Once the generic autonomous agent exists, create a concrete instance which:
- Has one handler that filters messages for the keyword “hello” and prints the whole
message to stdout
- Has one behaviour that generates random 2-word messages from an alphabet of
10 words (“hello”, “sun”, “world”, “space”, “moon”, “crypto”, “sky”, “ocean”, “universe”,
“human”) every 2 seconds

Run two instances of your concrete agents where the InBox of agent 1 is the OutBox of
agent 2 and vice versa.

Write one unit and one integration (both agents) test.

When you’re finished, submit a PR in a Github repository of your choosing and assign GH
handles dvilelaf - angrybayblade - dagacha as reviewers.

### Notes

- Imagine you’re submitting a PR to a production project you’re working on
- The design is light on details intentionally – we encourage you to make
clarifications and request changes
- Actual designs in the course of normal work at Valory would be much more fleshed
out
- We recommend using only pure Python3 and its standard libraries
- Include some notes in the PR to walk us through any choices you needed to make
or any feedback you have on the design
- Ideally you would spend no more than 3 hours implementing
4 changes: 2 additions & 2 deletions packages/author/agents/demo_agent/aea-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ protocols:
skills:
- valory/abstract_abci:0.1.0:bafybeihat4giyc4bz6zopvahcj4iw53356pbtwfn7p4d5yflwly2qhahum
- valory/abstract_round_abci:0.1.0:bafybeih3enhagoql7kzpeyzzu2scpkif6y3ubakpralfnwxcvxexdyvy5i
- author/demo_abci:0.1.0:bafybeiezb2qjtas6n6fgfsmfrdgt6odolnloc7f74jliyjmwhdfwkizi4q
- author/demo_chained_abci:0.1.0:bafybeie7h7ppfa4pr3igpxs6t56mf5jmptfy5arxnwyjdjacwjyn2jlgxq
- author/demo_abci:0.1.0:bafybeihvzwb2jx7yfgxjojvnqdx4s5hzv35kic4llhl4w2edfhi6g5l2cm
- author/demo_chained_abci:0.1.0:bafybeibwc7s2btdtjliwmrs7tmrirgkmlqdnuunqvev3ivlo5sglyqhsvm
- valory/registration_abci:0.1.0:bafybeiek7zcsxbucjwzgqfftafhfrocvc7q4yxllh2q44jeemsjxg3rcfm
- valory/reset_pause_abci:0.1.0:bafybeidw4mbx3os3hmv7ley7b3g3gja7ydpitr7mxbjpwzxin2mzyt5yam
- valory/termination_abci:0.1.0:bafybeihq6qtbwt6i53ayqym63vhjexkcppy26gguzhhjqywfmiuqghvv44
Expand Down
2 changes: 1 addition & 1 deletion packages/author/services/demo_service/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ license: Apache-2.0
fingerprint:
README.md: bafybeicxq7czrb2v2mzpvytqc53eawd6kwe7vq2lh5kn63hhxrj3fqodne
fingerprint_ignore_patterns: []
agent: author/demo_agent:0.1.0:bafybeigrsujrdafgxx65c5faaiv5qcvvre4m3thttg23oea3ktwue4vifm
agent: author/demo_agent:0.1.0:bafybeihtfz5zmorkmtn65u27gc464fcg6l44rqvn2yn72u23xty25aws7a
number_of_agents: 1
deployment:
agent:
Expand Down
20 changes: 19 additions & 1 deletion packages/author/skills/demo_abci/behaviours.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

"""This package contains round behaviours of DemoAbciApp."""

import random
from abc import ABC
from typing import Generator, Set, Type, cast

Expand Down Expand Up @@ -55,6 +56,20 @@ def local_state(self) -> SharedState:
return cast(SharedState, self.context.state)


DICTIONARY = (
"hello",
"sun",
"world",
"space",
"moon",
"crypto",
"sky",
"ocean",
"universe",
"human",
)


class DemoBehaviour(DemoBaseBehaviour): # pylint: disable=too-many-ancestors
"""DemoBehaviour"""

Expand All @@ -65,7 +80,10 @@ def async_act(self) -> Generator:

with self.context.benchmark_tool.measure(self.behaviour_id).local():
sender = self.context.agent_address
payload_content = "Hello world!"
# `nosec` because random is used not for cryptography here:
payload_content = " ".join(
random.choice(DICTIONARY) for _ in range(2) # nosec B311
)
self.context.logger.info(payload_content)
payload = DemoPayload(sender=sender, content=payload_content)

Expand Down
15 changes: 14 additions & 1 deletion packages/author/skills/demo_abci/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

"""This module contains the handlers for the skill of DemoAbciApp."""

from base64 import b64decode

from aea.protocols.base import Message

from packages.valory.skills.abstract_round_abci.handlers import (
ABCIRoundHandler as BaseABCIRoundHandler,
)
Expand All @@ -42,10 +46,19 @@
)


ABCIHandler = BaseABCIRoundHandler
HttpHandler = BaseHttpHandler
SigningHandler = BaseSigningHandler
LedgerApiHandler = BaseLedgerApiHandler
ContractApiHandler = BaseContractApiHandler
TendermintHandler = BaseTendermintHandler
IpfsHandler = BaseIpfsHandler


class ABCIHandler(BaseABCIRoundHandler):
"""Handler which checks in message contains `hello`"""

def handle(self, message: Message) -> None:
"""Handler function"""
body = message.json().get("body")
if body and ("hello" in b64decode(body).decode("utf-8")):
print(message)
6 changes: 3 additions & 3 deletions packages/author/skills/demo_abci/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ license: Apache-2.0
aea_version: '>=1.0.0, <2.0.0'
fingerprint:
__init__.py: bafybeic7j5uouzatm6jcd5izcdgucm4yppu76s6iuczke6rzzhwbwbvgue
behaviours.py: bafybeief6s2s24nmvsm4fdxghi6eht52xpw244fm5hmsckjoug7kq6gc6q
behaviours.py: bafybeicxwqw3qezs3atrp5gwzlsnd42wwhlxftfai3kb2ijxsmoz6lb7jq
dialogues.py: bafybeifs775q3j2ipnpzn4pwcom47hhgyg7czqqy5gvjo5ov7ijjrqzeju
fsm_specification.yaml: bafybeidfv2py64y2atghoe2kzgvtxtotk2srmfrvtg4tju4gdahqelfaau
handlers.py: bafybeibmjubpjicmnrkz7up4hbvpl3wowewr7naz7bvolsxnihzbsjjnxi
handlers.py: bafybeiajxtfugq7u4aalqdvbtta6voewwlpnjzihgeh6xlm3e34knjukv4
models.py: bafybeia3rbywe77d73q6lgvu2x7lfpzms54f7r3xh4hna5m6zeclwnnlvu
payloads.py: bafybeidrewrz5c4evdahnhf25z2txsqemlcoga4wnfj73yseqkvwcerf4u
rounds.py: bafybeib65fq7ywwmecv57qjm3kx6jx5hb3r6ikrlll66poqdxcnsinxnxq
Expand Down Expand Up @@ -111,7 +111,7 @@ models:
consensus_threshold: null
safe_contract_address: '0x0000000000000000000000000000000000000000'
share_tm_config_on_startup: false
sleep_time: 1
sleep_time: 2
tendermint_check_sleep_delay: 3
tendermint_com_url: http://localhost:8080
tendermint_max_retries: 5
Expand Down
2 changes: 1 addition & 1 deletion packages/author/skills/demo_chained_abci/skill.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ skills:
- valory/registration_abci:0.1.0:bafybeiek7zcsxbucjwzgqfftafhfrocvc7q4yxllh2q44jeemsjxg3rcfm
- valory/reset_pause_abci:0.1.0:bafybeidw4mbx3os3hmv7ley7b3g3gja7ydpitr7mxbjpwzxin2mzyt5yam
- valory/termination_abci:0.1.0:bafybeihq6qtbwt6i53ayqym63vhjexkcppy26gguzhhjqywfmiuqghvv44
- author/demo_abci:0.1.0:bafybeiezb2qjtas6n6fgfsmfrdgt6odolnloc7f74jliyjmwhdfwkizi4q
- author/demo_abci:0.1.0:bafybeihvzwb2jx7yfgxjojvnqdx4s5hzv35kic4llhl4w2edfhi6g5l2cm
- valory/transaction_settlement_abci:0.1.0:bafybeigtzlk4uakmd54rxnznorcrstsr52kta474lgrnvx5ovr546vj7sq
behaviours:
main:
Expand Down
8 changes: 4 additions & 4 deletions packages/packages.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"dev": {
"skill/author/demo_abci/0.1.0": "bafybeiezb2qjtas6n6fgfsmfrdgt6odolnloc7f74jliyjmwhdfwkizi4q",
"skill/author/demo_chained_abci/0.1.0": "bafybeie7h7ppfa4pr3igpxs6t56mf5jmptfy5arxnwyjdjacwjyn2jlgxq",
"agent/author/demo_agent/0.1.0": "bafybeigrsujrdafgxx65c5faaiv5qcvvre4m3thttg23oea3ktwue4vifm",
"service/author/demo_service/0.1.0": "bafybeieqjcnnmvw4ewqtw5qi77o4zpvxsrczue74lkwr26pqwstjcyrwhm"
"skill/author/demo_abci/0.1.0": "bafybeihvzwb2jx7yfgxjojvnqdx4s5hzv35kic4llhl4w2edfhi6g5l2cm",
"skill/author/demo_chained_abci/0.1.0": "bafybeibwc7s2btdtjliwmrs7tmrirgkmlqdnuunqvev3ivlo5sglyqhsvm",
"agent/author/demo_agent/0.1.0": "bafybeihtfz5zmorkmtn65u27gc464fcg6l44rqvn2yn72u23xty25aws7a",
"service/author/demo_service/0.1.0": "bafybeidezjo4gngynchki46rc2p464t4yc54irr5cownkpbozlufhcxhqu"
},
"third_party": {
"protocol/open_aea/signing/1.0.0": "bafybeihv62fim3wl2bayavfcg3u5e5cxu3b7brtu4cn5xoxd6lqwachasi",
Expand Down
132 changes: 132 additions & 0 deletions tests/test_demo_abci.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
# -*- coding: utf-8 -*-
# ------------------------------------------------------------------------------
#
# Copyright 2021-2023 Valory AG
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# ------------------------------------------------------------------------------

"""Test module for Project A."""

import json
from io import StringIO
from logging import WARNING, getLogger
from typing import Generator
from unittest import mock

import pytest
from aea.protocols.base import Message, Serializer

from packages.author.skills.demo_abci.behaviours import DemoBehaviour
from packages.author.skills.demo_abci.handlers import ABCIHandler
from packages.author.skills.demo_abci.payloads import DemoPayload


class DummyContextManager:
def __enter__(self, *_args, **_kwargs) -> None:
pass

def __exit__(self, *_args, **_kwargs) -> None:
pass


class TestContext:
agent_address = "foobar"
logger = getLogger("test")
logger.setLevel(WARNING)

class benchmark_tool:
class measure:
def __init__(self, *_args, **_kwargs) -> None:
pass

class local(DummyContextManager):
pass

class consensus(DummyContextManager):
pass

class state:
class round_sequence:
current_round_id = "demo_round"
last_round_id = "demo_round"
current_round_height = 123


class TestSerializer(Serializer):
@staticmethod
def encode(message: Message) -> bytes:
return json.dumps(message._body).encode("utf-8")

@staticmethod
def decode(input_text: bytes) -> Message:
return Message.from_json(json.loads(input_text.decode("utf-8")))


class TestMessage(Message):
serializer = TestSerializer


def handler_test_common(text: str) -> str:
message = TestMessage({
"dialogue_reference": text,
})
out_file = StringIO()
with mock.patch("sys.stdout", new=out_file):
ABCIHandler(name="smth", skill_context=TestContext).handle(message)
return out_file.getvalue()


def test_handler_triggered() -> None:
test_output = handler_test_common("hello")
assert "Message(sender=None,to=None,dialogue_reference=hello)" in test_output


def test_handler_skipped() -> None:
test_output = handler_test_common("spam")
assert "Message(" not in test_output


def test_behaviour() -> None:
test_result = None

def mock_transaction(_self: DemoBehaviour, payload: DemoPayload) -> Generator:
nonlocal test_result
test_result = payload
yield

def round_end(_self: DemoBehaviour) -> Generator:
yield

with (
mock.patch(
"packages.author.skills.demo_abci.behaviours.DemoBehaviour.send_a2a_transaction",
mock_transaction,
),
mock.patch(
"packages.author.skills.demo_abci.behaviours.DemoBehaviour.wait_until_round_end",
round_end,
),
):
for _ in DemoBehaviour(name="smth", skill_context=TestContext).async_act():
pass

assert isinstance(test_result, DemoPayload)
assert len(test_result.content.split(" ")) == 2 # 2 words


@pytest.mark.e2e
def test_dummy_integration() -> None:
"""Dummy test integration."""
assert True