Spaces:
Sleeping
Sleeping
Merge pull request #7 from macrocosm-os/feature/openapi
Browse files- README.md +5 -1
- common/__init__.py +0 -0
- middlewares.py β common/middlewares.py +8 -0
- common/schemas.py +29 -0
- utils.py β common/utils.py +0 -0
- requirements.txt +2 -1
- server.py +44 -13
README.md
CHANGED
@@ -77,7 +77,7 @@ At present, the API provides two endpoints: `/chat` (live) and `/echo` (test).
|
|
77 |
|
78 |
`/chat` is used to chat with the network and receive a response. It requires a JSON payload structured as per the QueryValidatorParams class.
|
79 |
The request payload requires the following parameters encapsulated within the [`QueryValidatorParams`](./validators/base.py) data class:
|
80 |
-
- `
|
81 |
- `exclude: List[str]`: A list of roles or agents to exclude from querying.
|
82 |
- `roles: List[str]`: The roles of the agents to query.
|
83 |
- `messages: List[str]`: The messages to be sent to the network.
|
@@ -124,6 +124,10 @@ Final JSON:
|
|
124 |
```
|
125 |
After verifying that the server is responding to requests locally, you can test the server on a remote machine.
|
126 |
|
|
|
|
|
|
|
|
|
127 |
### Troubleshooting
|
128 |
|
129 |
If you do not receive a response from the server, check that the server is running and that the port is open on the server. You can open the port using the following commands:
|
|
|
77 |
|
78 |
`/chat` is used to chat with the network and receive a response. It requires a JSON payload structured as per the QueryValidatorParams class.
|
79 |
The request payload requires the following parameters encapsulated within the [`QueryValidatorParams`](./validators/base.py) data class:
|
80 |
+
- `k: int`: The number of miners from which to request responses.
|
81 |
- `exclude: List[str]`: A list of roles or agents to exclude from querying.
|
82 |
- `roles: List[str]`: The roles of the agents to query.
|
83 |
- `messages: List[str]`: The messages to be sent to the network.
|
|
|
124 |
```
|
125 |
After verifying that the server is responding to requests locally, you can test the server on a remote machine.
|
126 |
|
127 |
+
## OpenAPI
|
128 |
+
To access OpenAPI specification, go to:
|
129 |
+
[http://localhost:10000/docs](http://localhost:10000/docs)
|
130 |
+
|
131 |
### Troubleshooting
|
132 |
|
133 |
If you do not receive a response from the server, check that the server is running and that the port is open on the server. You can open the port using the following commands:
|
common/__init__.py
ADDED
File without changes
|
middlewares.py β common/middlewares.py
RENAMED
@@ -8,6 +8,10 @@ EXPECTED_ACCESS_KEY = os.environ.get("EXPECTED_ACCESS_KEY")
|
|
8 |
|
9 |
@middleware
|
10 |
async def api_key_middleware(request: Request, handler):
|
|
|
|
|
|
|
|
|
11 |
# Logging the request
|
12 |
bt.logging.info(f"Handling {request.method} request to {request.path}")
|
13 |
|
@@ -23,6 +27,10 @@ async def api_key_middleware(request: Request, handler):
|
|
23 |
|
24 |
@middleware
|
25 |
async def json_parsing_middleware(request: Request, handler):
|
|
|
|
|
|
|
|
|
26 |
try:
|
27 |
# Parsing JSON data from the request
|
28 |
request["data"] = await request.json()
|
|
|
8 |
|
9 |
@middleware
|
10 |
async def api_key_middleware(request: Request, handler):
|
11 |
+
if request.path.startswith("/docs") or request.path.startswith("/static/swagger"):
|
12 |
+
# Skip checks when accessing OpenAPI documentation.
|
13 |
+
return await handler(request)
|
14 |
+
|
15 |
# Logging the request
|
16 |
bt.logging.info(f"Handling {request.method} request to {request.path}")
|
17 |
|
|
|
27 |
|
28 |
@middleware
|
29 |
async def json_parsing_middleware(request: Request, handler):
|
30 |
+
if request.path.startswith("/docs") or request.path.startswith("/static/swagger"):
|
31 |
+
# Skip checks when accessing OpenAPI documentation.
|
32 |
+
return await handler(request)
|
33 |
+
|
34 |
try:
|
35 |
# Parsing JSON data from the request
|
36 |
request["data"] = await request.json()
|
common/schemas.py
ADDED
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from marshmallow import Schema, fields
|
2 |
+
|
3 |
+
|
4 |
+
class QueryChatSchema(Schema):
|
5 |
+
k = fields.Int(description="The number of miners from which to request responses.")
|
6 |
+
exclude = fields.List(fields.Str(), description="A list of roles or agents to exclude from querying.")
|
7 |
+
roles = fields.List(fields.Str(), required=True, description="The roles of the agents to query.")
|
8 |
+
messages = fields.List(fields.Str(), required=True, description="The messages to be sent to the network.")
|
9 |
+
timeout = fields.Int(description="The time in seconds to wait for a response.")
|
10 |
+
prefer = fields.Str(description="The preferred response format, can be either 'longest' or 'shortest'.")
|
11 |
+
sampling_mode = fields.Str(
|
12 |
+
description="The mode of sampling to use, defaults to 'random'. Can be either 'random' or 'top_incentive'.")
|
13 |
+
|
14 |
+
|
15 |
+
class StreamChunkSchema(Schema):
|
16 |
+
delta = fields.Str(required=True, description="The new chunk of response received.")
|
17 |
+
finish_reason = fields.Str(description="The reason for the response completion, if applicable.")
|
18 |
+
accumulated_chunks = fields.List(fields.Str(), description="All accumulated chunks of responses.")
|
19 |
+
accumulated_chunks_timings = fields.List(fields.Float(), description="Timing for each chunk received.")
|
20 |
+
timestamp = fields.Str(required=True, description="The timestamp at which the chunk was processed.")
|
21 |
+
sequence_number = fields.Int(required=True, description="A sequential identifier for the response part.")
|
22 |
+
selected_uid = fields.Int(required=True, description="The identifier for the selected response source.")
|
23 |
+
|
24 |
+
|
25 |
+
class StreamErrorSchema(Schema):
|
26 |
+
error = fields.Str(required=True, description="Description of the error occurred.")
|
27 |
+
timestamp = fields.Str(required=True, description="The timestamp of the error.")
|
28 |
+
sequence_number = fields.Int(required=True, description="A sequential identifier for the error.")
|
29 |
+
finish_reason = fields.Str(default="error", description="Indicates an error completion.")
|
utils.py β common/utils.py
RENAMED
File without changes
|
requirements.txt
CHANGED
@@ -1,3 +1,4 @@
|
|
1 |
git+https://github.com/opentensor/prompting.git@features/move-validator-into-prompting
|
2 |
aiohttp
|
3 |
-
deprecated
|
|
|
|
1 |
git+https://github.com/opentensor/prompting.git@features/move-validator-into-prompting
|
2 |
aiohttp
|
3 |
+
deprecated
|
4 |
+
aiohttp_apispec>=2.2.3
|
server.py
CHANGED
@@ -1,15 +1,25 @@
|
|
1 |
import asyncio
|
2 |
-
|
3 |
import bittensor as bt
|
4 |
from aiohttp import web
|
5 |
-
from
|
6 |
-
|
7 |
-
|
8 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9 |
async def chat(request: web.Request) -> web.StreamResponse:
|
10 |
-
"""
|
11 |
-
Chat endpoint for the validator.
|
12 |
-
"""
|
13 |
params = QueryValidatorParams.from_request(request)
|
14 |
|
15 |
# Access the validator from the application context
|
@@ -19,6 +29,14 @@ async def chat(request: web.Request) -> web.StreamResponse:
|
|
19 |
return response
|
20 |
|
21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
async def echo_stream(request: web.Request) -> web.StreamResponse:
|
23 |
return await utils.echo_stream(request)
|
24 |
|
@@ -27,19 +45,32 @@ class ValidatorApplication(web.Application):
|
|
27 |
def __init__(self, validator_instance=None, *args, **kwargs):
|
28 |
super().__init__(*args, **kwargs)
|
29 |
|
30 |
-
self["validator"] = (
|
31 |
-
validator_instance if validator_instance else S1ValidatorAPI()
|
32 |
-
)
|
33 |
|
34 |
# Add middlewares to application
|
35 |
-
self.add_routes(
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
self.setup_middlewares()
|
37 |
# TODO: Enable rewarding and other features
|
38 |
|
39 |
def setup_middlewares(self):
|
|
|
40 |
self.middlewares.append(json_parsing_middleware)
|
41 |
self.middlewares.append(api_key_middleware)
|
42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
43 |
|
44 |
def main(run_aio_app=True, test=False) -> None:
|
45 |
loop = asyncio.get_event_loop()
|
@@ -48,7 +79,7 @@ def main(run_aio_app=True, test=False) -> None:
|
|
48 |
# Instantiate the application with the actual validator
|
49 |
bt.logging.info("Starting validator application.")
|
50 |
validator_app = ValidatorApplication()
|
51 |
-
bt.logging.success(
|
52 |
|
53 |
try:
|
54 |
web.run_app(validator_app, port=port, loop=loop)
|
|
|
1 |
import asyncio
|
2 |
+
|
3 |
import bittensor as bt
|
4 |
from aiohttp import web
|
5 |
+
from aiohttp_apispec import docs, request_schema, response_schema, setup_aiohttp_apispec, validation_middleware
|
6 |
+
|
7 |
+
from common import utils
|
8 |
+
from common.middlewares import api_key_middleware, json_parsing_middleware
|
9 |
+
from common.schemas import QueryChatSchema, StreamChunkSchema, StreamErrorSchema
|
10 |
+
from validators import QueryValidatorParams, S1ValidatorAPI, ValidatorAPI
|
11 |
+
|
12 |
+
|
13 |
+
@docs(
|
14 |
+
tags=["Prompting API"],
|
15 |
+
summary="Chat",
|
16 |
+
description="Chat endpoint."
|
17 |
+
)
|
18 |
+
@request_schema(QueryChatSchema)
|
19 |
+
@response_schema(StreamChunkSchema, 200)
|
20 |
+
@response_schema(StreamErrorSchema, 400)
|
21 |
async def chat(request: web.Request) -> web.StreamResponse:
|
22 |
+
"""Chat endpoint for the validator"""
|
|
|
|
|
23 |
params = QueryValidatorParams.from_request(request)
|
24 |
|
25 |
# Access the validator from the application context
|
|
|
29 |
return response
|
30 |
|
31 |
|
32 |
+
@docs(
|
33 |
+
tags=["Prompting API"],
|
34 |
+
summary="Echo test",
|
35 |
+
description="Echo endpoint for testing purposes."
|
36 |
+
)
|
37 |
+
@request_schema(QueryChatSchema)
|
38 |
+
@response_schema(StreamChunkSchema, 200)
|
39 |
+
@response_schema(StreamErrorSchema, 400)
|
40 |
async def echo_stream(request: web.Request) -> web.StreamResponse:
|
41 |
return await utils.echo_stream(request)
|
42 |
|
|
|
45 |
def __init__(self, validator_instance=None, *args, **kwargs):
|
46 |
super().__init__(*args, **kwargs)
|
47 |
|
48 |
+
self["validator"] = validator_instance if validator_instance else S1ValidatorAPI()
|
|
|
|
|
49 |
|
50 |
# Add middlewares to application
|
51 |
+
self.add_routes(
|
52 |
+
[
|
53 |
+
web.post("/chat/", chat),
|
54 |
+
web.post("/echo/", echo_stream),
|
55 |
+
]
|
56 |
+
)
|
57 |
+
self.setup_openapi()
|
58 |
self.setup_middlewares()
|
59 |
# TODO: Enable rewarding and other features
|
60 |
|
61 |
def setup_middlewares(self):
|
62 |
+
self.middlewares.append(validation_middleware)
|
63 |
self.middlewares.append(json_parsing_middleware)
|
64 |
self.middlewares.append(api_key_middleware)
|
65 |
|
66 |
+
def setup_openapi(self):
|
67 |
+
setup_aiohttp_apispec(
|
68 |
+
app=self,
|
69 |
+
title="Prompting API",
|
70 |
+
url="/docs/swagger.json",
|
71 |
+
swagger_path="/docs",
|
72 |
+
)
|
73 |
+
|
74 |
|
75 |
def main(run_aio_app=True, test=False) -> None:
|
76 |
loop = asyncio.get_event_loop()
|
|
|
79 |
# Instantiate the application with the actual validator
|
80 |
bt.logging.info("Starting validator application.")
|
81 |
validator_app = ValidatorApplication()
|
82 |
+
bt.logging.success("Validator app initialized successfully", validator_app)
|
83 |
|
84 |
try:
|
85 |
web.run_app(validator_app, port=port, loop=loop)
|