From a9dbabb5e39b1d65ee69ee5db067d547520dbfc1 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 13:33:00 +0100 Subject: [PATCH 01/19] sample agent commit --- README.md | 20 +++++++++++++- app/routes/posts.py | 46 +++++++++++++++++++++++-------- sampleAgent/sampleAgent.py | 50 ++++++++++++++++++++++++++++++++++ tests/conftest.py | 11 ++++++++ tests/test_post_query.py | 21 +++++++++++--- tests/test_receive_response.py | 12 ++++++-- 6 files changed, 141 insertions(+), 19 deletions(-) create mode 100644 sampleAgent/sampleAgent.py diff --git a/README.md b/README.md index c693b5e..dd82a32 100644 --- a/README.md +++ b/README.md @@ -1 +1,19 @@ -# main +# Prerequisits: + +python 3.12 and higher and poetry installed + +### To start the repo + +Run `poetry install` + +cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. + +Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it(in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py` + +### To run tests run this: + +`poetry run pytest -s` + +# If I forget to ask: + +- atm the agent address is hardcoded in the `agentQuery`. Would it be better if the agent on startup would send it's address to the app or write it into a file from which we'd retrieve it? diff --git a/app/routes/posts.py b/app/routes/posts.py index b286fd4..859b330 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -1,21 +1,30 @@ +import json from datetime import datetime from enum import IntEnum, StrEnum -from typing import Any, List, Optional +from typing import Any, List, Optional, Union +from uuid import UUID import httpx + +# from agentQuery.agentQuery import agent_query from core.config import settings from fastapi import APIRouter, HTTPException from fastapi.responses import JSONResponse -from pydantic import BaseModel +from pydantic import Field from uagents import Model +from uagents.query import query veritableUrl = settings.VERITABLE_URL peerUrl = settings.PEER_URL +# class QueryFromPeerApi(Model): +# message:dict -class Query(BaseModel): - message: dict - +class DrcpQueryFromPeerApi(Model): + jsonrpc: str + method: str = Field(default="query", const=True) + params: List[Any] + id: Union[str, UUID] class DrpcRequestObject(Model): jsonrpc: str @@ -60,8 +69,10 @@ class DrpcState(StrEnum): # RPC Response example # --> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2} # <-- {"jsonrpc": "2.0", "result": -19, "id": 2} -class Response(Model): - message: dict +class DrpcResponseForVeritableApi(Model): + jsonrpc: str + result: Any + id: Union[str, UUID] class DrpcEvent(Model): @@ -78,14 +89,20 @@ class DrpcEvent(Model): router = APIRouter() +AGENT_ADDRESS = 'agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj' +async def agent_query(req): + response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) + data = json.loads(response.decode_payload()) + return data -async def postToVeritable(req: Query) -> JSONResponse: + +async def postToVeritable(req: DrcpQueryFromPeerApi) -> JSONResponse: async with httpx.AsyncClient() as client: response = await client.post(f"{veritableUrl}/drcp/request", json=req) return [response.status, response.json()] -async def postResponseToVeritable(req: Response) -> JSONResponse: +async def postResponseToVeritable(req: DrpcResponseForVeritableApi) -> JSONResponse: async with httpx.AsyncClient() as client: response = client.post(f"{veritableUrl}/drcp/response", json=req) return [response.status, response.json()] @@ -102,10 +119,15 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: response = await client.post(f"{peerUrl}/receive-query", json=req) return [response.status, response.json()] - +# Query from PeerApi to query agent & veritable @router.post("/send-query", name="test-name", status_code=202) -async def send_query(req: Query): # need to define Query +async def send_query(req: DrcpQueryFromPeerApi): try: + agentQueryResp = await agent_query(req) + expected_response = [{'Successful query response from the Sample Agent'}] + if agentQueryResp != expected_response: + raise ValueError(f"Query Agent returned unexpected response. Response returned: {agentQueryResp}") + #do sth based on the agentQueryResponse?? response = await postToVeritable(req) if response[0] != "202": raise ValueError("Response status is not 202") @@ -156,7 +178,7 @@ async def drpc_event_handler(req: DrpcEvent): @router.post( "/receive-response", name="receive-response", status_code=200 ) # this receives response from chainvine and it forwards info to veritable -async def receive_response(resp: Response): # basic RPC response +async def receive_response(resp: DrpcResponseForVeritableApi): # basic RPC response try: response = await postResponseToVeritable(resp) if response[0] != "200": diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py new file mode 100644 index 0000000..aa5c0b9 --- /dev/null +++ b/sampleAgent/sampleAgent.py @@ -0,0 +1,50 @@ +from typing import Any, List, Union +from uuid import UUID + +from pydantic import Field +from uagents import Agent, Context, Model +from uagents.setup import fund_agent_if_low + + +class TestRequest(Model): + message: str + +class DrcpQueryFromPeerApi(Model): + jsonrpc: str + method: str = Field(default="query", const=True) + params: List[Any] + id: Union[str, UUID] + +class Response(Model): + text: str + +# test info: + # agent address: agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj + # agent wallet address: fetch1dc5s9wmerlsvdq9gxp76xxldk8jzp8l9zlyakr +agent = Agent( + name="sample_agent_name", + seed="sample_agent_seed", + port=8001, + endpoint="http://localhost:8001/submit", +) +fund_agent_if_low(agent.wallet.address()) + + +@agent.on_event("startup") +async def startup(ctx: Context): + ctx.logger.info(f"Starting up {agent.name}") + ctx.logger.info(f"With address: {agent.address}") + ctx.logger.info(f"And wallet address: {agent.wallet.address()}") + +# query from PeerAPI +@agent.on_query(model=DrcpQueryFromPeerApi, replies={Response}) +async def query_handler(ctx: Context, sender: str, _query: TestRequest): + ctx.logger.info("Query received by the Sample Agent") + try: + # do something here with the _query + ctx.logger.info(_query) + await ctx.send(sender, Response(text="Successful query response from the Sample Agent")) + except Exception: + await ctx.send(sender, Response(text="fail")) + +agent.run() \ No newline at end of file diff --git a/tests/conftest.py b/tests/conftest.py index 68bca79..f3d253c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,6 +41,17 @@ def test_post_query_success_mock_202(mocker) -> None: {}, ], ) +@pytest.fixture +def test_agent_query_mock(mocker) -> None: + """ + Test return data for the 202 response fromquery agent. + """ + mocker.patch( + "routes.posts.agent_query", + return_value=[ + {"Successful query response from the Sample Agent"}, + ], + ) @pytest.fixture diff --git a/tests/test_post_query.py b/tests/test_post_query.py index b984526..eba5070 100644 --- a/tests/test_post_query.py +++ b/tests/test_post_query.py @@ -10,11 +10,19 @@ async def test_post_query( app: FastAPI, client: AsyncClient, test_post_query_success_mock_202, + test_agent_query_mock, ) -> None: """ ... """ - payload = {"message": {"content": "some test message"}} + payload = { + "jsonrpc": "string", + "method": "query", + "params": [ + "string" + ], + "id": "string" +} response_pat = await client.post( app.url_path_for("test-name"), json=payload, @@ -23,8 +31,6 @@ async def test_post_query( # sad path tests - - async def test_post_query_wrong_body( app: FastAPI, client: AsyncClient, @@ -48,7 +54,14 @@ async def test_post_query_failed_500( """ ... """ - payload = {"message": {"content": "some test message"}} + payload = { + "jsonrpc": "string", + "method": "query", + "params": [ + "string" + ], + "id": "string" +} response_pat = await client.post( app.url_path_for("test-name"), json=payload, diff --git a/tests/test_receive_response.py b/tests/test_receive_response.py index 35f4aea..962cf6f 100644 --- a/tests/test_receive_response.py +++ b/tests/test_receive_response.py @@ -16,7 +16,11 @@ async def test_receive_response( """ ... """ - payload = {"message": {"content": "some test message"}} + payload = { + "jsonrpc": "string", + "result": "string", + "id": "string" +} response_pat = await client.post( app.url_path_for("receive-response"), json=payload, @@ -35,7 +39,11 @@ async def test_receive_response_fail_500( """ ... """ - payload = {"message": {"content": "some test message"}} + payload = { + "jsonrpc": "string", + "result": "string", + "id": "string" +} response_pat = await client.post( app.url_path_for("receive-response"), json=payload, From 826e65e555e4a528dab09d72f73b49ca11043335 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 13:34:04 +0100 Subject: [PATCH 02/19] format --- app/routes/posts.py | 35 ++++++++++++++++++++-------------- sampleAgent/sampleAgent.py | 33 +++++++++++++++++++------------- tests/conftest.py | 2 ++ tests/test_post_query.py | 24 ++++++++++------------- tests/test_receive_response.py | 12 ++---------- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/app/routes/posts.py b/app/routes/posts.py index 859b330..ec5f189 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -20,11 +20,13 @@ # class QueryFromPeerApi(Model): # message:dict -class DrcpQueryFromPeerApi(Model): - jsonrpc: str - method: str = Field(default="query", const=True) - params: List[Any] - id: Union[str, UUID] + +class DrcpQueryFromPeerApi(Model): + jsonrpc: str + method: str = Field(default="query", const=True) + params: List[Any] + id: Union[str, UUID] + class DrpcRequestObject(Model): jsonrpc: str @@ -70,9 +72,9 @@ class DrpcState(StrEnum): # --> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2} # <-- {"jsonrpc": "2.0", "result": -19, "id": 2} class DrpcResponseForVeritableApi(Model): - jsonrpc: str - result: Any - id: Union[str, UUID] + jsonrpc: str + result: Any + id: Union[str, UUID] class DrpcEvent(Model): @@ -89,7 +91,9 @@ class DrpcEvent(Model): router = APIRouter() -AGENT_ADDRESS = 'agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj' +AGENT_ADDRESS = "agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj" + + async def agent_query(req): response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) data = json.loads(response.decode_payload()) @@ -119,15 +123,18 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: response = await client.post(f"{peerUrl}/receive-query", json=req) return [response.status, response.json()] -# Query from PeerApi to query agent & veritable + +# Query from PeerApi to query agent & veritable @router.post("/send-query", name="test-name", status_code=202) -async def send_query(req: DrcpQueryFromPeerApi): +async def send_query(req: DrcpQueryFromPeerApi): try: agentQueryResp = await agent_query(req) - expected_response = [{'Successful query response from the Sample Agent'}] + expected_response = [{"Successful query response from the Sample Agent"}] if agentQueryResp != expected_response: - raise ValueError(f"Query Agent returned unexpected response. Response returned: {agentQueryResp}") - #do sth based on the agentQueryResponse?? + raise ValueError( + f"Query Agent returned unexpected response. Response returned: {agentQueryResp}" + ) + # do sth based on the agentQueryResponse?? response = await postToVeritable(req) if response[0] != "202": raise ValueError("Response status is not 202") diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index aa5c0b9..783bca8 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -9,18 +9,21 @@ class TestRequest(Model): message: str -class DrcpQueryFromPeerApi(Model): - jsonrpc: str - method: str = Field(default="query", const=True) - params: List[Any] - id: Union[str, UUID] - + +class DrcpQueryFromPeerApi(Model): + jsonrpc: str + method: str = Field(default="query", const=True) + params: List[Any] + id: Union[str, UUID] + + class Response(Model): text: str + # test info: - # agent address: agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj - # agent wallet address: fetch1dc5s9wmerlsvdq9gxp76xxldk8jzp8l9zlyakr +# agent address: agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj +# agent wallet address: fetch1dc5s9wmerlsvdq9gxp76xxldk8jzp8l9zlyakr agent = Agent( name="sample_agent_name", seed="sample_agent_seed", @@ -29,13 +32,14 @@ class Response(Model): ) fund_agent_if_low(agent.wallet.address()) - + @agent.on_event("startup") async def startup(ctx: Context): ctx.logger.info(f"Starting up {agent.name}") ctx.logger.info(f"With address: {agent.address}") ctx.logger.info(f"And wallet address: {agent.wallet.address()}") - + + # query from PeerAPI @agent.on_query(model=DrcpQueryFromPeerApi, replies={Response}) async def query_handler(ctx: Context, sender: str, _query: TestRequest): @@ -43,8 +47,11 @@ async def query_handler(ctx: Context, sender: str, _query: TestRequest): try: # do something here with the _query ctx.logger.info(_query) - await ctx.send(sender, Response(text="Successful query response from the Sample Agent")) + await ctx.send( + sender, Response(text="Successful query response from the Sample Agent") + ) except Exception: await ctx.send(sender, Response(text="fail")) - -agent.run() \ No newline at end of file + + +agent.run() diff --git a/tests/conftest.py b/tests/conftest.py index f3d253c..747e3c4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,6 +41,8 @@ def test_post_query_success_mock_202(mocker) -> None: {}, ], ) + + @pytest.fixture def test_agent_query_mock(mocker) -> None: """ diff --git a/tests/test_post_query.py b/tests/test_post_query.py index eba5070..712f9df 100644 --- a/tests/test_post_query.py +++ b/tests/test_post_query.py @@ -16,13 +16,11 @@ async def test_post_query( ... """ payload = { - "jsonrpc": "string", - "method": "query", - "params": [ - "string" - ], - "id": "string" -} + "jsonrpc": "string", + "method": "query", + "params": ["string"], + "id": "string", + } response_pat = await client.post( app.url_path_for("test-name"), json=payload, @@ -55,13 +53,11 @@ async def test_post_query_failed_500( ... """ payload = { - "jsonrpc": "string", - "method": "query", - "params": [ - "string" - ], - "id": "string" -} + "jsonrpc": "string", + "method": "query", + "params": ["string"], + "id": "string", + } response_pat = await client.post( app.url_path_for("test-name"), json=payload, diff --git a/tests/test_receive_response.py b/tests/test_receive_response.py index 962cf6f..f33aaae 100644 --- a/tests/test_receive_response.py +++ b/tests/test_receive_response.py @@ -16,11 +16,7 @@ async def test_receive_response( """ ... """ - payload = { - "jsonrpc": "string", - "result": "string", - "id": "string" -} + payload = {"jsonrpc": "string", "result": "string", "id": "string"} response_pat = await client.post( app.url_path_for("receive-response"), json=payload, @@ -39,11 +35,7 @@ async def test_receive_response_fail_500( """ ... """ - payload = { - "jsonrpc": "string", - "result": "string", - "id": "string" -} + payload = {"jsonrpc": "string", "result": "string", "id": "string"} response_pat = await client.post( app.url_path_for("receive-response"), json=payload, From 025a7357c58c9ec5403b7748afcf92298a735bae Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 13:45:37 +0100 Subject: [PATCH 03/19] config --- README.md | 4 ++-- app/core/config.py | 1 + app/routes/posts.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index dd82a32..ed98902 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ python 3.12 and higher and poetry installed Run `poetry install` -cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. +Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it(in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py`. On startup agent prints it's own address -> please include this address in `core/config.py` -Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it(in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py` +cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. ### To run tests run this: diff --git a/app/core/config.py b/app/core/config.py index e95a0df..99ebd78 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -6,6 +6,7 @@ class AppSettings(BaseSettings): VERITABLE_URL: str = os.getenv("VERITABLE_URL", "http://localhost:3010") PEER_URL: str = os.getenv("PEER_URL", "http://localhost:3001/api") + AGENT_ADDRESS: str = os.getenv("AGENT_ADDRESS", "agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj") settings = AppSettings() diff --git a/app/routes/posts.py b/app/routes/posts.py index ec5f189..8b546c3 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -16,6 +16,7 @@ veritableUrl = settings.VERITABLE_URL peerUrl = settings.PEER_URL +AGENT_ADDRESS = settings.AGENT_ADDRESS # class QueryFromPeerApi(Model): # message:dict @@ -91,7 +92,6 @@ class DrpcEvent(Model): router = APIRouter() -AGENT_ADDRESS = "agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj" async def agent_query(req): From 5a1a414708cbf2d42bb511a3d17ef4a576fa39ae Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 13:48:48 +0100 Subject: [PATCH 04/19] format --- app/routes/posts.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/routes/posts.py b/app/routes/posts.py index 8b546c3..41d2f1e 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -93,7 +93,6 @@ class DrpcEvent(Model): router = APIRouter() - async def agent_query(req): response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) data = json.loads(response.decode_payload()) From ddf097176dab39dbe4b693d03a70ca6f70dd9a51 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 13:55:09 +0100 Subject: [PATCH 05/19] README update --- README.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ed98902..a8211f0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Prerequisits: +# Prerequisites: python 3.12 and higher and poetry installed @@ -6,14 +6,10 @@ python 3.12 and higher and poetry installed Run `poetry install` -Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it(in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py`. On startup agent prints it's own address -> please include this address in `core/config.py` +Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it (in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py`. On startup agent prints it's own address -> please include this address in `core/config.py` -cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. +Open new terminal window, cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. -### To run tests run this: +### To run tests: `poetry run pytest -s` - -# If I forget to ask: - -- atm the agent address is hardcoded in the `agentQuery`. Would it be better if the agent on startup would send it's address to the app or write it into a file from which we'd retrieve it? From 7488e76e792ffeee13348e66732afd1a430b8928 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 14:05:17 +0100 Subject: [PATCH 06/19] readme update --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a8211f0..e447330 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,14 @@ -# Prerequisites: +# nice-fetch-ai-adapter + +The purpose of this repository is to allow interaction between nice-agent-portal (PEER API), Veritable Peer and Cmbridge's 'intelligent agent'. +The endpoints in this repository are: +| endpoint | usage | +|----------|----------| +| "/send-query" | accepts query from Peer API, forwards it to Sample Agent & then to Veritable Peer | +| "/webhooks/drpc"| accepts posts from veritable Cloudagent and passes them to PeerApi. These are either queries for peerApi or query responses for peer API | +| "/receive-response" | This endpoint receives information from chainvine and passes it to Veritable as a response | + +### Prerequisites: python 3.12 and higher and poetry installed From f5fd3c2894c8060208646df87146f7dbf92e391b Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 14:06:50 +0100 Subject: [PATCH 07/19] readme update --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e447330..820ab89 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,14 @@ The purpose of this repository is to allow interaction between nice-agent-portal The endpoints in this repository are: | endpoint | usage | |----------|----------| -| "/send-query" | accepts query from Peer API, forwards it to Sample Agent & then to Veritable Peer | -| "/webhooks/drpc"| accepts posts from veritable Cloudagent and passes them to PeerApi. These are either queries for peerApi or query responses for peer API | -| "/receive-response" | This endpoint receives information from chainvine and passes it to Veritable as a response | +| "/send-query" | Accepts query from Peer API, forwards it to Sample Agent & then to Veritable Peer. | +| "/webhooks/drpc"| Accepts posts from veritable Cloudagent and passes them to PeerApi. These are either queries for peerApi or query responses for peer API. | +| "/receive-response" | This endpoint receives information from chainvine and passes it to Veritable as a response. | ### Prerequisites: -python 3.12 and higher and poetry installed +- python 3.12 or higher +- poetry installed ### To start the repo From c5cca1f81e9de697ff261e027e76ab134c053a28 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 14:08:29 +0100 Subject: [PATCH 08/19] comments --- app/routes/posts.py | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/app/routes/posts.py b/app/routes/posts.py index 41d2f1e..52b5224 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -5,8 +5,6 @@ from uuid import UUID import httpx - -# from agentQuery.agentQuery import agent_query from core.config import settings from fastapi import APIRouter, HTTPException from fastapi.responses import JSONResponse @@ -18,9 +16,6 @@ peerUrl = settings.PEER_URL AGENT_ADDRESS = settings.AGENT_ADDRESS -# class QueryFromPeerApi(Model): -# message:dict - class DrcpQueryFromPeerApi(Model): jsonrpc: str @@ -69,9 +64,6 @@ class DrpcState(StrEnum): Completed = ("completed",) -# RPC Response example -# --> {"jsonrpc": "2.0", "method": "subtract", "params": [23, 42], "id": 2} -# <-- {"jsonrpc": "2.0", "result": -19, "id": 2} class DrpcResponseForVeritableApi(Model): jsonrpc: str result: Any @@ -142,9 +134,8 @@ async def send_query(req: DrcpQueryFromPeerApi): raise HTTPException(status_code=500, detail=str(e)) -@router.post( - "/webhooks/drpc", name="webhooks-drpc", status_code=200 -) # from veritable cloudagent to peerAPI +# from veritable cloudagent to peerAPI +@router.post("/webhooks/drpc", name="webhooks-drpc", status_code=200) async def drpc_event_handler(req: DrpcEvent): try: req_dict = dict(req) @@ -181,10 +172,9 @@ async def drpc_event_handler(req: DrpcEvent): raise HTTPException(status_code=500, detail=str(e)) -@router.post( - "/receive-response", name="receive-response", status_code=200 -) # this receives response from chainvine and it forwards info to veritable -async def receive_response(resp: DrpcResponseForVeritableApi): # basic RPC response +# this receives response from chainvine and it forwards info to veritable +@router.post("/receive-response", name="receive-response", status_code=200) +async def receive_response(resp: DrpcResponseForVeritableApi): try: response = await postResponseToVeritable(resp) if response[0] != "200": From fd177620ac9e45fc68c1fcf197211cf2dcecdc07 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Wed, 22 May 2024 16:38:52 +0100 Subject: [PATCH 09/19] adjustments --- .github/workflows/release.yaml | 17 ++++++++++++++++- app/routes/posts.py | 16 +++++++++------- sampleAgent/sampleAgent.py | 2 +- tests/conftest.py | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 1788ca8..626b2d6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -44,6 +44,21 @@ jobs: run: poetry install --no-interaction --no-ansi - name: Lint run: poetry run flake8 + tests: + name: Run tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install poetry + run: pipx install poetry + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: poetry + - name: Install Packages + run: poetry install --no-interaction --no-ansi + - name: Run tests + run: poetry run pytest -v -s -W ignore::DeprecationWarning check-version: name: "Check version" runs-on: ubuntu-latest @@ -63,7 +78,7 @@ jobs: package_manager: "poetry" publish: name: "Publish package" - needs: [preconditions, lint, dependency-check, tests, check-version] + needs: [preconditions, lint, tests, check-version] runs-on: ubuntu-latest # comment the line above to force publish if: ${{ needs.check-version.outputs.is_new_version == 'true' }} diff --git a/app/routes/posts.py b/app/routes/posts.py index 52b5224..8e3e9a2 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -21,7 +21,7 @@ class DrcpQueryFromPeerApi(Model): jsonrpc: str method: str = Field(default="query", const=True) params: List[Any] - id: Union[str, UUID] + id: str | int class DrpcRequestObject(Model): @@ -67,7 +67,7 @@ class DrpcState(StrEnum): class DrpcResponseForVeritableApi(Model): jsonrpc: str result: Any - id: Union[str, UUID] + id: str | int class DrpcEvent(Model): @@ -88,7 +88,7 @@ class DrpcEvent(Model): async def agent_query(req): response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) data = json.loads(response.decode_payload()) - return data + return [data] async def postToVeritable(req: DrcpQueryFromPeerApi) -> JSONResponse: @@ -99,7 +99,7 @@ async def postToVeritable(req: DrcpQueryFromPeerApi) -> JSONResponse: async def postResponseToVeritable(req: DrpcResponseForVeritableApi) -> JSONResponse: async with httpx.AsyncClient() as client: - response = client.post(f"{veritableUrl}/drcp/response", json=req) + response =await client.post(f"{veritableUrl}/drcp/response", json=req) return [response.status, response.json()] @@ -120,13 +120,14 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: async def send_query(req: DrcpQueryFromPeerApi): try: agentQueryResp = await agent_query(req) - expected_response = [{"Successful query response from the Sample Agent"}] + expected_response = [{"text":"Successful query response from the Sample Agent"}] if agentQueryResp != expected_response: raise ValueError( f"Query Agent returned unexpected response. Response returned: {agentQueryResp}" ) # do sth based on the agentQueryResponse?? - response = await postToVeritable(req) + req_dict = dict(req) + response = await postToVeritable(req_dict) if response[0] != "202": raise ValueError("Response status is not 202") return response @@ -176,7 +177,8 @@ async def drpc_event_handler(req: DrpcEvent): @router.post("/receive-response", name="receive-response", status_code=200) async def receive_response(resp: DrpcResponseForVeritableApi): try: - response = await postResponseToVeritable(resp) + resp_dict= dict(resp) + response = await postResponseToVeritable(resp_dict) if response[0] != "200": raise ValueError("Response status is not 200") return response diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index 783bca8..9b6e360 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -14,7 +14,7 @@ class DrcpQueryFromPeerApi(Model): jsonrpc: str method: str = Field(default="query", const=True) params: List[Any] - id: Union[str, UUID] + id: str | int class Response(Model): diff --git a/tests/conftest.py b/tests/conftest.py index 747e3c4..8adca23 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,7 +51,7 @@ def test_agent_query_mock(mocker) -> None: mocker.patch( "routes.posts.agent_query", return_value=[ - {"Successful query response from the Sample Agent"}, + {"text":"Successful query response from the Sample Agent"}, ], ) From 9a5ca421aeae943b3e2eca0eac9c90c0b0037b47 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Thu, 23 May 2024 10:03:43 +0100 Subject: [PATCH 10/19] fixed types --- README.md | 83 +++++++++++++++++++++++++++++++++++--- app/routes/posts.py | 29 ++++--------- sampleAgent/sampleAgent.py | 12 +++--- 3 files changed, 89 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 820ab89..b6a5ab9 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,83 @@ # nice-fetch-ai-adapter -The purpose of this repository is to allow interaction between nice-agent-portal (PEER API), Veritable Peer and Cmbridge's 'intelligent agent'. +The purpose of this repository is to allow interaction between nice-agent-portal (PEER API), Veritable Peer and Cambridge's 'intelligent agent'. + +### Endpoints + The endpoints in this repository are: -| endpoint | usage | -|----------|----------| -| "/send-query" | Accepts query from Peer API, forwards it to Sample Agent & then to Veritable Peer. | -| "/webhooks/drpc"| Accepts posts from veritable Cloudagent and passes them to PeerApi. These are either queries for peerApi or query responses for peer API. | -| "/receive-response" | This endpoint receives information from chainvine and passes it to Veritable as a response. | +| endpoint |HTTP Methods| usage | +|----------|----|----------| +| "/send-query" |POST| Accepts query from Peer API, forwards it to Sample Agent & then to Veritable Peer. | +| "/webhooks/drpc"|POST| Accepts posts from veritable Cloudagent and passes them to PeerApi. These are either queries for peerApi or query responses for peer API. | +| "/receive-response" |POST| This endpoint receives information from chainvine and passes it to Veritable as a response. | + +### Payload schemas: + +#### POST/send-query schema inbound: + +``` +{ + "jsonrpc": "string", + "method": "string", + "params": [ + "string" + ], + "id": "string" +} +``` + +#### POST/send-query schema outbound: + +-> will depend on drcp response +for now `[202, {}]` + +#### POST/webhooks/drpc schema inbound: + +- This schema can only include either `response` or `request`. +- If `request` is present, `role` must be `server` and if `response` is present `role` must be `client` + +``` +{ + "createdAt": "2024-05-23T08:23:49.183Z", + "request": { + "jsonrpc": "string", + "method": "string", + "params": [ + "string" + ], + "id": "string" + }, + "response": { + "jsonrpc": "string", + "result": "string", + "error": { + "code": -32601, + "message": "string", + "data": "string" + }, + "id": "string" + }, + "connectionId": "string", + "role": "client" OR "server", + "state": "request-sent", + "threadId": "string", + "id": "string" +} +``` + +#### POST/webhooks/drpc schema outbound: + +-> status 202 request received + +#### POST/receive-response schema inbound: + +``` +{ + "jsonrpc": "string", + "result": "string", + "id": "string" +} +``` ### Prerequisites: diff --git a/app/routes/posts.py b/app/routes/posts.py index 8e3e9a2..ac0f8fc 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -1,14 +1,12 @@ import json from datetime import datetime from enum import IntEnum, StrEnum -from typing import Any, List, Optional, Union -from uuid import UUID +from typing import Any, List, Optional import httpx from core.config import settings from fastapi import APIRouter, HTTPException from fastapi.responses import JSONResponse -from pydantic import Field from uagents import Model from uagents.query import query @@ -17,18 +15,11 @@ AGENT_ADDRESS = settings.AGENT_ADDRESS -class DrcpQueryFromPeerApi(Model): - jsonrpc: str - method: str = Field(default="query", const=True) - params: List[Any] - id: str | int - - class DrpcRequestObject(Model): jsonrpc: str method: str params: Optional[List | object] - id: Optional[str | int] + id: str | int class DrpcErrorCode(IntEnum): @@ -50,7 +41,7 @@ class DrpcResponseObject(Model): jsonrpc: str result: Optional[Any] error: Optional[DrpcResponseError] - id: Optional[str | int] + id: str | int class DrpcRole(StrEnum): @@ -64,12 +55,6 @@ class DrpcState(StrEnum): Completed = ("completed",) -class DrpcResponseForVeritableApi(Model): - jsonrpc: str - result: Any - id: str | int - - class DrpcEvent(Model): createdAt: datetime request: Optional[DrpcRequestObject | List[DrpcRequestObject]] @@ -91,13 +76,13 @@ async def agent_query(req): return [data] -async def postToVeritable(req: DrcpQueryFromPeerApi) -> JSONResponse: +async def postToVeritable(req: DrpcRequestObject) -> JSONResponse: async with httpx.AsyncClient() as client: response = await client.post(f"{veritableUrl}/drcp/request", json=req) return [response.status, response.json()] -async def postResponseToVeritable(req: DrpcResponseForVeritableApi) -> JSONResponse: +async def postResponseToVeritable(req: DrpcResponseObject) -> JSONResponse: async with httpx.AsyncClient() as client: response =await client.post(f"{veritableUrl}/drcp/response", json=req) return [response.status, response.json()] @@ -117,7 +102,7 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: # Query from PeerApi to query agent & veritable @router.post("/send-query", name="test-name", status_code=202) -async def send_query(req: DrcpQueryFromPeerApi): +async def send_query(req: DrpcRequestObject): try: agentQueryResp = await agent_query(req) expected_response = [{"text":"Successful query response from the Sample Agent"}] @@ -175,7 +160,7 @@ async def drpc_event_handler(req: DrpcEvent): # this receives response from chainvine and it forwards info to veritable @router.post("/receive-response", name="receive-response", status_code=200) -async def receive_response(resp: DrpcResponseForVeritableApi): +async def receive_response(resp: DrpcResponseObject): try: resp_dict= dict(resp) response = await postResponseToVeritable(resp_dict) diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index 9b6e360..d2723ea 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -1,7 +1,5 @@ -from typing import Any, List, Union -from uuid import UUID +from typing import List, Optional -from pydantic import Field from uagents import Agent, Context, Model from uagents.setup import fund_agent_if_low @@ -10,10 +8,10 @@ class TestRequest(Model): message: str -class DrcpQueryFromPeerApi(Model): +class DrpcRequestObject(Model): jsonrpc: str - method: str = Field(default="query", const=True) - params: List[Any] + method: str + params: Optional[List | object] id: str | int @@ -41,7 +39,7 @@ async def startup(ctx: Context): # query from PeerAPI -@agent.on_query(model=DrcpQueryFromPeerApi, replies={Response}) +@agent.on_query(model=DrpcRequestObject, replies={Response}) async def query_handler(ctx: Context, sender: str, _query: TestRequest): ctx.logger.info("Query received by the Sample Agent") try: From 3ca94edcad1319d64d2e05b0b9852be972922a1c Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Thu, 23 May 2024 14:04:49 +0100 Subject: [PATCH 11/19] updated types --- README.md | 5 +++++ app/routes/posts.py | 11 ++++++++--- sampleAgent/sampleAgent.py | 12 +++++------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b6a5ab9..f0da149 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,11 @@ for now `[202, {}]` { "jsonrpc": "string", "result": "string", + "error": { + "code": -32601, + "message": "string", + "data": "string" + }, "id": "string" } ``` diff --git a/app/routes/posts.py b/app/routes/posts.py index ac0f8fc..297843b 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -65,12 +65,14 @@ class DrpcEvent(Model): threadId: str id: str _tags: dict - +class AgentRequest(Model): + params: List[str] + id: str router = APIRouter() -async def agent_query(req): +async def agent_query(req:AgentRequest): response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) data = json.loads(response.decode_payload()) return [data] @@ -104,7 +106,10 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: @router.post("/send-query", name="test-name", status_code=202) async def send_query(req: DrpcRequestObject): try: - agentQueryResp = await agent_query(req) + print(req) + agentRequest: AgentRequest = {"params": req.params, "id": req.id} + agentQueryResp = await agent_query(agentRequest) + print(agentQueryResp) expected_response = [{"text":"Successful query response from the Sample Agent"}] if agentQueryResp != expected_response: raise ValueError( diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index d2723ea..a78adde 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import List from uagents import Agent, Context, Model from uagents.setup import fund_agent_if_low @@ -8,11 +8,9 @@ class TestRequest(Model): message: str -class DrpcRequestObject(Model): - jsonrpc: str - method: str - params: Optional[List | object] - id: str | int +class AgentRequest(Model): + params: List[str] + id: str class Response(Model): @@ -39,7 +37,7 @@ async def startup(ctx: Context): # query from PeerAPI -@agent.on_query(model=DrpcRequestObject, replies={Response}) +@agent.on_query(model=AgentRequest, replies={Response}) async def query_handler(ctx: Context, sender: str, _query: TestRequest): ctx.logger.info("Query received by the Sample Agent") try: From 157fc641102f8675c8ff9f2efa5df5b21fc6c721 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Thu, 23 May 2024 16:12:44 +0100 Subject: [PATCH 12/19] types fixed --- app/routes/posts.py | 48 ++++++++++++++++++++++++++++++++------ sampleAgent/sampleAgent.py | 8 ++++++- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/app/routes/posts.py b/app/routes/posts.py index 297843b..f2202f4 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -1,7 +1,7 @@ import json from datetime import datetime from enum import IntEnum, StrEnum -from typing import Any, List, Optional +from typing import Any, List, Optional, Union import httpx from core.config import settings @@ -41,7 +41,7 @@ class DrpcResponseObject(Model): jsonrpc: str result: Optional[Any] error: Optional[DrpcResponseError] - id: str | int + id: Union[str | int] class DrpcRole(StrEnum): @@ -65,6 +65,7 @@ class DrpcEvent(Model): threadId: str id: str _tags: dict + class AgentRequest(Model): params: List[str] id: str @@ -73,8 +74,13 @@ class AgentRequest(Model): async def agent_query(req:AgentRequest): + print("=========") + print(req) response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) + print("-----") + print(response) data = json.loads(response.decode_payload()) + print(data) return [data] @@ -101,27 +107,55 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: response = await client.post(f"{peerUrl}/receive-query", json=req) return [response.status, response.json()] +async def create_error_response(id: Union[str, int], error_code: DrpcErrorCode, error_message: str, error_data: Optional[Any] = None) -> DrpcResponseObject: + return DrpcResponseObject( + jsonrpc="2.0", + id=id, + error=DrpcResponseError( + code=error_code, + message=error_message, + data=error_data + ), + result=None + ) # Query from PeerApi to query agent & veritable @router.post("/send-query", name="test-name", status_code=202) async def send_query(req: DrpcRequestObject): try: - print(req) - agentRequest: AgentRequest = {"params": req.params, "id": req.id} + if req.method !="query": + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="Only supported method is query" + )) + agentRequest= AgentRequest(params=req.params, id=str(req.id)) + print(type(agentRequest)) + print(type(dict(agentRequest))) agentQueryResp = await agent_query(agentRequest) print(agentQueryResp) expected_response = [{"text":"Successful query response from the Sample Agent"}] if agentQueryResp != expected_response: raise ValueError( - f"Query Agent returned unexpected response. Response returned: {agentQueryResp}" - ) + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message=f"Query Agent returned unexpected response. Response returned: {agentQueryResp}" + )) # do sth based on the agentQueryResponse?? req_dict = dict(req) response = await postToVeritable(req_dict) if response[0] != "202": - raise ValueError("Response status is not 202") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="Response status is not 202" + )) return response except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index a78adde..02806ff 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -1,4 +1,4 @@ -from typing import List +from typing import List, Optional from uagents import Agent, Context, Model from uagents.setup import fund_agent_if_low @@ -7,6 +7,11 @@ class TestRequest(Model): message: str +class DrpcRequestObject(Model): + jsonrpc: str + method: str + params: Optional[List | object] + id: str | int class AgentRequest(Model): params: List[str] @@ -43,6 +48,7 @@ async def query_handler(ctx: Context, sender: str, _query: TestRequest): try: # do something here with the _query ctx.logger.info(_query) + print("in agent") await ctx.send( sender, Response(text="Successful query response from the Sample Agent") ) From f77c1211db04b44e247ae7ca71207ef7727622e4 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Thu, 23 May 2024 16:46:07 +0100 Subject: [PATCH 13/19] error messages --- app/core/config.py | 5 +- app/routes/posts.py | 110 ++++++++++++++++++++++++++----------- sampleAgent/sampleAgent.py | 3 +- tests/conftest.py | 2 +- 4 files changed, 86 insertions(+), 34 deletions(-) diff --git a/app/core/config.py b/app/core/config.py index 99ebd78..fe4625b 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -6,7 +6,10 @@ class AppSettings(BaseSettings): VERITABLE_URL: str = os.getenv("VERITABLE_URL", "http://localhost:3010") PEER_URL: str = os.getenv("PEER_URL", "http://localhost:3001/api") - AGENT_ADDRESS: str = os.getenv("AGENT_ADDRESS", "agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj") + AGENT_ADDRESS: str = os.getenv( + "AGENT_ADDRESS", + "agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj", + ) settings = AppSettings() diff --git a/app/routes/posts.py b/app/routes/posts.py index f2202f4..693ce86 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -65,15 +65,17 @@ class DrpcEvent(Model): threadId: str id: str _tags: dict - + + class AgentRequest(Model): params: List[str] id: str + router = APIRouter() -async def agent_query(req:AgentRequest): +async def agent_query(req: AgentRequest): print("=========") print(req) response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) @@ -92,7 +94,7 @@ async def postToVeritable(req: DrpcRequestObject) -> JSONResponse: async def postResponseToVeritable(req: DrpcResponseObject) -> JSONResponse: async with httpx.AsyncClient() as client: - response =await client.post(f"{veritableUrl}/drcp/response", json=req) + response = await client.post(f"{veritableUrl}/drcp/response", json=req) return [response.status, response.json()] @@ -107,55 +109,65 @@ async def peerReceivesQuery(req: DrpcEvent) -> JSONResponse: response = await client.post(f"{peerUrl}/receive-query", json=req) return [response.status, response.json()] -async def create_error_response(id: Union[str, int], error_code: DrpcErrorCode, error_message: str, error_data: Optional[Any] = None) -> DrpcResponseObject: + +async def create_error_response( + id: Union[str, int], + error_code: DrpcErrorCode, + error_message: str, + error_data: Optional[Any] = None, +) -> DrpcResponseObject: return DrpcResponseObject( jsonrpc="2.0", id=id, error=DrpcResponseError( - code=error_code, - message=error_message, - data=error_data + code=error_code, message=error_message, data=error_data ), - result=None + result=None, ) + # Query from PeerApi to query agent & veritable @router.post("/send-query", name="test-name", status_code=202) async def send_query(req: DrpcRequestObject): try: - if req.method !="query": + if req.method != "query": raise ValueError( await create_error_response( - id=req.id, - error_code=DrpcErrorCode.invalid_request, - error_message="Only supported method is query" - )) - agentRequest= AgentRequest(params=req.params, id=str(req.id)) + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="Only supported method is query", + ) + ) + agentRequest = AgentRequest(params=req.params, id=str(req.id)) print(type(agentRequest)) print(type(dict(agentRequest))) agentQueryResp = await agent_query(agentRequest) print(agentQueryResp) - expected_response = [{"text":"Successful query response from the Sample Agent"}] + expected_response = [ + {"text": "Successful query response from the Sample Agent"} + ] if agentQueryResp != expected_response: raise ValueError( await create_error_response( - id=req.id, - error_code=DrpcErrorCode.invalid_request, - error_message=f"Query Agent returned unexpected response. Response returned: {agentQueryResp}" - )) + id=req.id, + error_code=DrpcErrorCode.server_error, + error_message=f"Query Agent returned unexpected response. Response returned: {agentQueryResp}", + ) + ) # do sth based on the agentQueryResponse?? req_dict = dict(req) response = await postToVeritable(req_dict) if response[0] != "202": raise ValueError( await create_error_response( - id=req.id, - error_code=DrpcErrorCode.invalid_request, - error_message="Response status is not 202" - )) + id=req.id, + error_code=DrpcErrorCode.server_error, + error_message="Response status is not 202", + ) + ) return response except Exception as e: - + raise HTTPException(status_code=500, detail=str(e)) @@ -179,19 +191,49 @@ async def drpc_event_handler(req: DrpcEvent): response_check = req_dict["response"] role_check = req_dict["role"] if request_check and response_check: - raise ValueError("JSON body cannot contain both 'request' and 'response'") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="JSON body cannot contain both 'request' and 'response'", + ) + ) if request_check and role_check != "server": - raise ValueError("If 'request' is present, 'role' must be 'server'") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="If 'request' is present, 'role' must be 'server'", + ) + ) if response_check and role_check != "client": - raise ValueError("If 'response' is present, 'role' must be 'client'") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="If 'response' is present, 'role' must be 'client'", + ) + ) if role_check == "client": response = await peerReceivesResponse(req_dict) elif role_check == "server": response = await peerReceivesQuery(req_dict) else: - raise ValueError("Error in request body.") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.invalid_request, + error_message="Error in request body.", + ) + ) if response[0] != "200": - raise ValueError("Response status is not 200") + raise ValueError( + await create_error_response( + id=req.id, + error_code=DrpcErrorCode.server_error, + error_message=f"Response status from Peer Api is not 200. Response status: {response[0]} and body: {response[1]}", + ) + ) return response except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -201,10 +243,16 @@ async def drpc_event_handler(req: DrpcEvent): @router.post("/receive-response", name="receive-response", status_code=200) async def receive_response(resp: DrpcResponseObject): try: - resp_dict= dict(resp) + resp_dict = dict(resp) response = await postResponseToVeritable(resp_dict) if response[0] != "200": - raise ValueError("Response status is not 200") + raise ValueError( + await create_error_response( + id=resp.id, + error_code=DrpcErrorCode.server_error, + error_message=f"Response status from Peer Api is not 200. Response status: {response[0]} and body: {response[1]}", + ) + ) return response except Exception as e: raise HTTPException(status_code=500, detail=str(e)) diff --git a/sampleAgent/sampleAgent.py b/sampleAgent/sampleAgent.py index 02806ff..523020f 100644 --- a/sampleAgent/sampleAgent.py +++ b/sampleAgent/sampleAgent.py @@ -7,12 +7,14 @@ class TestRequest(Model): message: str + class DrpcRequestObject(Model): jsonrpc: str method: str params: Optional[List | object] id: str | int + class AgentRequest(Model): params: List[str] id: str @@ -48,7 +50,6 @@ async def query_handler(ctx: Context, sender: str, _query: TestRequest): try: # do something here with the _query ctx.logger.info(_query) - print("in agent") await ctx.send( sender, Response(text="Successful query response from the Sample Agent") ) diff --git a/tests/conftest.py b/tests/conftest.py index 8adca23..ab348d1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -51,7 +51,7 @@ def test_agent_query_mock(mocker) -> None: mocker.patch( "routes.posts.agent_query", return_value=[ - {"text":"Successful query response from the Sample Agent"}, + {"text": "Successful query response from the Sample Agent"}, ], ) From 56798ae15f44f430ff53bbb63980bd312b610867 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Thu, 23 May 2024 16:50:19 +0100 Subject: [PATCH 14/19] drpc --- README.md | 2 +- tests/conftest.py | 6 +++--- tests/test_drcp_event_handler.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index f0da149..8f68d60 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ The endpoints in this repository are: #### POST/send-query schema outbound: --> will depend on drcp response +-> will depend on drpc response for now `[202, {}]` #### POST/webhooks/drpc schema inbound: diff --git a/tests/conftest.py b/tests/conftest.py index ab348d1..7437e28 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -85,7 +85,7 @@ def test_receive_response_fail_mock_500(mocker) -> None: @pytest.fixture -def test_drcp_event_handler_response_success_mock_200(mocker) -> None: +def test_drpc_event_handler_response_success_mock_200(mocker) -> None: """ Test return data for forwarding data from the "/webhooks/drpc" endpoint to /receive-response endpoint in peerAPI. """ @@ -99,7 +99,7 @@ def test_drcp_event_handler_response_success_mock_200(mocker) -> None: @pytest.fixture -def test_drcp_event_handler_query_success_mock_200(mocker) -> None: +def test_drpc_event_handler_query_success_mock_200(mocker) -> None: """ Test return data for forwarding data from the "/webhooks/drpc" endpoint to /receive-query endpoint in peerAPI. """ @@ -113,7 +113,7 @@ def test_drcp_event_handler_query_success_mock_200(mocker) -> None: @pytest.fixture -def test_drcp_event_handler_query_fail_mock_500(mocker) -> None: +def test_drpc_event_handler_query_fail_mock_500(mocker) -> None: """ Test failed return data for forwarding data from the "/webhooks/drpc" endpoint to /receive-query endpoint in peerAPI. """ diff --git a/tests/test_drcp_event_handler.py b/tests/test_drcp_event_handler.py index d493577..c9b8227 100644 --- a/tests/test_drcp_event_handler.py +++ b/tests/test_drcp_event_handler.py @@ -6,10 +6,10 @@ # happy path tests -async def test_drcp_event_handler_response( +async def test_drpc_event_handler_response( app: FastAPI, client: AsyncClient, - test_drcp_event_handler_response_success_mock_200, + test_drpc_event_handler_response_success_mock_200, ) -> None: """ ... @@ -35,10 +35,10 @@ async def test_drcp_event_handler_response( assert response_pat.status_code == status.HTTP_200_OK -async def test_drcp_event_handler_query( +async def test_drpc_event_handler_query( app: FastAPI, client: AsyncClient, - test_drcp_event_handler_query_success_mock_200, + test_drpc_event_handler_query_success_mock_200, ) -> None: """ ... @@ -114,7 +114,7 @@ async def test_receive_response_500( assert response_pat.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR -async def test_drcp_event_handler_query_wrong_role( +async def test_drpc_event_handler_query_wrong_role( app: FastAPI, client: AsyncClient, ) -> None: @@ -142,7 +142,7 @@ async def test_drcp_event_handler_query_wrong_role( assert response_pat.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR -async def test_drcp_event_handler_response_wrong_role( +async def test_drpc_event_handler_response_wrong_role( app: FastAPI, client: AsyncClient, ) -> None: @@ -170,10 +170,10 @@ async def test_drcp_event_handler_response_wrong_role( assert response_pat.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR -async def test_drcp_event_handler_query_fail_500( +async def test_drpc_event_handler_query_fail_500( app: FastAPI, client: AsyncClient, - test_drcp_event_handler_query_fail_mock_500, + test_drpc_event_handler_query_fail_mock_500, ) -> None: """ ... From 6a634855e1c8102eb9920c53c4e13438536e2f21 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Fri, 24 May 2024 09:23:56 +0100 Subject: [PATCH 15/19] format --- app/routes/posts.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/app/routes/posts.py b/app/routes/posts.py index 693ce86..f142785 100644 --- a/app/routes/posts.py +++ b/app/routes/posts.py @@ -76,25 +76,20 @@ class AgentRequest(Model): async def agent_query(req: AgentRequest): - print("=========") - print(req) response = await query(destination=AGENT_ADDRESS, message=req, timeout=15.0) - print("-----") - print(response) data = json.loads(response.decode_payload()) - print(data) return [data] async def postToVeritable(req: DrpcRequestObject) -> JSONResponse: async with httpx.AsyncClient() as client: - response = await client.post(f"{veritableUrl}/drcp/request", json=req) + response = await client.post(f"{veritableUrl}/drpc/request", json=req) return [response.status, response.json()] async def postResponseToVeritable(req: DrpcResponseObject) -> JSONResponse: async with httpx.AsyncClient() as client: - response = await client.post(f"{veritableUrl}/drcp/response", json=req) + response = await client.post(f"{veritableUrl}/drpc/response", json=req) return [response.status, response.json()] @@ -139,10 +134,7 @@ async def send_query(req: DrpcRequestObject): ) ) agentRequest = AgentRequest(params=req.params, id=str(req.id)) - print(type(agentRequest)) - print(type(dict(agentRequest))) agentQueryResp = await agent_query(agentRequest) - print(agentQueryResp) expected_response = [ {"text": "Successful query response from the Sample Agent"} ] @@ -231,7 +223,7 @@ async def drpc_event_handler(req: DrpcEvent): await create_error_response( id=req.id, error_code=DrpcErrorCode.server_error, - error_message=f"Response status from Peer Api is not 200. Response status: {response[0]} and body: {response[1]}", + error_message=f"Response status from Peer Api is not 200. Response status:{response[0]}, body: {response[1]}", ) ) return response @@ -250,7 +242,7 @@ async def receive_response(resp: DrpcResponseObject): await create_error_response( id=resp.id, error_code=DrpcErrorCode.server_error, - error_message=f"Response status from Peer Api is not 200. Response status: {response[0]} and body: {response[1]}", + error_message=f"Response status from Peer Api is not 200. Response status:{response[0]}, body: {response[1]}", ) ) return response From d8533c633fa77f8d25ea8e743e578bbe92c29d94 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Fri, 24 May 2024 09:46:05 +0100 Subject: [PATCH 16/19] running from run.py --- README.md | 6 +++--- run.py | 41 +++++++++++++++++++++++++++++++++++++++++ sampleAgent/__init__.py | 0 3 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 run.py create mode 100644 sampleAgent/__init__.py diff --git a/README.md b/README.md index 8f68d60..2f4e5d9 100644 --- a/README.md +++ b/README.md @@ -93,9 +93,9 @@ for now `[202, {}]` Run `poetry install` -Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. In order to start it (in a new terminal window), please navigate to the `SampleAgent` directory and run: `python sampleAgent.py`. On startup agent prints it's own address -> please include this address in `core/config.py` - -Open new terminal window, cd into app directory and run `uvicorn main:app ` which starts the app with swagger interface on `http://0.0.0.0:8000/docs`. +Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. +In order to bring up the repo run `python run.py` from your terminla (in root directory). This will bring up both the Sample fetch.ai agent and our app with swagger interface on `http://0.0.0.0:8000/docs`. +On startup agent prints it's own address -> please include this address in `core/config.py`(if it is different to the one hardcoded there). ### To run tests: diff --git a/run.py b/run.py new file mode 100644 index 0000000..c3d2b91 --- /dev/null +++ b/run.py @@ -0,0 +1,41 @@ +import os +import signal +import subprocess +import sys +import time + + +def start_uvicorn(): + os.chdir("app") + return subprocess.Popen([sys.executable, "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]) + +def start_fetch_agent(): + os.chdir("..") + return subprocess.Popen([sys.executable, "sampleAgent/sampleAgent.py"]) + +def main(): + # Start Uvicorn server + uvicorn_process = start_uvicorn() + print("Uvicorn server started.") + time.sleep(5) + + # Start Fetch.ai agent + fetch_agent_process = start_fetch_agent() + print("Fetch.ai agent started.") + + # Handle termination signals to gracefully shut down both processes + def signal_handler(sig, frame): + print('Terminating processes...') + uvicorn_process.terminate() + fetch_agent_process.terminate() + sys.exit(0) + + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + # Wait for processes to complete + uvicorn_process.wait() + fetch_agent_process.wait() + +if __name__ == "__main__": + main() diff --git a/sampleAgent/__init__.py b/sampleAgent/__init__.py new file mode 100644 index 0000000..e69de29 From bd295683a066b333ff060048086e5913116f7112 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Fri, 24 May 2024 11:08:51 +0100 Subject: [PATCH 17/19] startup from one file --- README.md | 2 ++ run.py | 19 +++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2f4e5d9..5ee47ca 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,8 @@ The endpoints in this repository are: #### POST/send-query schema inbound: +-> we only support method `query` for now + ``` { "jsonrpc": "string", diff --git a/run.py b/run.py index c3d2b91..e57c06e 100644 --- a/run.py +++ b/run.py @@ -7,12 +7,26 @@ def start_uvicorn(): os.chdir("app") - return subprocess.Popen([sys.executable, "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload"]) + return subprocess.Popen( + [ + sys.executable, + "-m", + "uvicorn", + "main:app", + "--host", + "0.0.0.0", + "--port", + "8000", + "--reload", + ] + ) + def start_fetch_agent(): os.chdir("..") return subprocess.Popen([sys.executable, "sampleAgent/sampleAgent.py"]) + def main(): # Start Uvicorn server uvicorn_process = start_uvicorn() @@ -25,7 +39,7 @@ def main(): # Handle termination signals to gracefully shut down both processes def signal_handler(sig, frame): - print('Terminating processes...') + print("Terminating processes...") uvicorn_process.terminate() fetch_agent_process.terminate() sys.exit(0) @@ -37,5 +51,6 @@ def signal_handler(sig, frame): uvicorn_process.wait() fetch_agent_process.wait() + if __name__ == "__main__": main() From 14912e9b5b3cb482d41ad960ab149a3b87d69ec0 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Fri, 24 May 2024 11:42:31 +0100 Subject: [PATCH 18/19] readme updates --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 5ee47ca..d224ad5 100644 --- a/README.md +++ b/README.md @@ -19,19 +19,16 @@ The endpoints in this repository are: ``` { - "jsonrpc": "string", - "method": "string", - "params": [ - "string" - ], + "jsonrpc": "2.0", + "method": "query", + "params": [{}], "id": "string" } ``` #### POST/send-query schema outbound: --> will depend on drpc response -for now `[202, {}]` +-> status 202 request received #### POST/webhooks/drpc schema inbound: @@ -42,22 +39,20 @@ for now `[202, {}]` { "createdAt": "2024-05-23T08:23:49.183Z", "request": { - "jsonrpc": "string", - "method": "string", - "params": [ - "string" - ], + "jsonrpc": "2.0", + "method": "query", + "params": [{}], "id": "string" }, "response": { - "jsonrpc": "string", - "result": "string", + "jsonrpc": "2.0", + "result": {}, "error": { "code": -32601, "message": "string", "data": "string" }, - "id": "string" + "id": }, "connectionId": "string", "role": "client" OR "server", @@ -75,17 +70,22 @@ for now `[202, {}]` ``` { - "jsonrpc": "string", - "result": "string", - "error": { + jsonrpc: '2.0', + result: {}, + id: + "error": { "code": -32601, "message": "string", "data": "string" }, - "id": "string" } + ``` +#### POST/receive-response schema outbound: + +-> status 202 request received + ### Prerequisites: - python 3.12 or higher @@ -96,7 +96,7 @@ for now `[202, {}]` Run `poetry install` Some endpoints require interaction with fetch.ai agent. We have included such `SampleAgent` in this repo. -In order to bring up the repo run `python run.py` from your terminla (in root directory). This will bring up both the Sample fetch.ai agent and our app with swagger interface on `http://0.0.0.0:8000/docs`. +In order to bring up the repo run `python run.py` from your terminal (in root directory). This will bring up both the Sample fetch.ai agent and our app with swagger interface on `http://0.0.0.0:8000/docs`. On startup agent prints it's own address -> please include this address in `core/config.py`(if it is different to the one hardcoded there). ### To run tests: From fd4906ce064838093f60d6eed3c715d1cf6ed059 Mon Sep 17 00:00:00 2001 From: Helena Adamkova <58051865+Ellenn-A@users.noreply.github.com> Date: Fri, 24 May 2024 11:48:14 +0100 Subject: [PATCH 19/19] readme update --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index d224ad5..cdca7fc 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,15 @@ The purpose of this repository is to allow interaction between nice-agent-portal (PEER API), Veritable Peer and Cambridge's 'intelligent agent'. +### Envars + +Can be found in `core>config.py`. +|ENV|required|default| +|--|------|----------| +|VERITABLE_URL|Y|http://localhost:3010| +|PEER_URL|Y| http://localhost:3001/api| +|AGENT_ADDRESS|Y|agent1qt8q20p0vsp7y5dkuehwkpmsppvynxv8esg255fwz6el68rftvt5klpyeuj| + ### Endpoints The endpoints in this repository are: