Dmytro Bobrenko commited on
Commit
65526b0
·
1 Parent(s): 32f9f6f

Add OpenAPI

Browse files
Files changed (5) hide show
  1. README.md +6 -1
  2. middlewares.py +8 -0
  3. requirements.txt +2 -1
  4. schemas.py +29 -0
  5. server.py +44 -8
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
- - `k_miners: 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,6 +124,11 @@ 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
+ ## Accessing OpenAPI Documentation
128
+ To access OpenAPI documentation, go to:
129
+ [http://localhost:10000/docs](http://localhost:10000/docs)
130
+
131
+
132
  ### Troubleshooting
133
 
134
  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:
middlewares.py CHANGED
@@ -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()
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.3.3
schemas.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from marshmallow import Schema, fields
2
+
3
+
4
+ class QueryValidatorParamsSchema(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.")
server.py CHANGED
@@ -2,14 +2,29 @@ import asyncio
2
  import utils
3
  import bittensor as bt
4
  from aiohttp import web
 
 
 
 
 
 
 
5
  from validators import S1ValidatorAPI, QueryValidatorParams, ValidatorAPI
6
  from middlewares import api_key_middleware, json_parsing_middleware
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 +34,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 +50,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([web.post("/chat/", chat), web.post("/echo/", echo_stream)])
 
 
 
 
 
 
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 +84,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(f"Validator app initialized successfully", validator_app)
52
 
53
  try:
54
  web.run_app(validator_app, port=port, loop=loop)
 
2
  import utils
3
  import bittensor as bt
4
  from aiohttp import web
5
+ from aiohttp_apispec import (
6
+ setup_aiohttp_apispec,
7
+ validation_middleware,
8
+ docs,
9
+ request_schema,
10
+ response_schema,
11
+ )
12
  from validators import S1ValidatorAPI, QueryValidatorParams, ValidatorAPI
13
  from middlewares import api_key_middleware, json_parsing_middleware
14
 
15
+ from schemas import QueryValidatorParamsSchema, StreamChunkSchema, StreamErrorSchema
16
 
17
+
18
+ @docs(
19
+ tags=["Prompting API"],
20
+ summary="Chat with the network",
21
+ description="Chat endpoint for the validator.",
22
+ )
23
+ @request_schema(QueryValidatorParamsSchema)
24
+ @response_schema(StreamChunkSchema, 200)
25
+ @response_schema(StreamErrorSchema, 400)
26
  async def chat(request: web.Request) -> web.StreamResponse:
27
+ """Chat endpoint for the validator"""
 
 
28
  params = QueryValidatorParams.from_request(request)
29
 
30
  # Access the validator from the application context
 
34
  return response
35
 
36
 
37
+ @docs(
38
+ tags=["Prompting API"],
39
+ summary="Echo test",
40
+ description="Echo endpoint for testing purposes.",
41
+ )
42
+ @request_schema(QueryValidatorParamsSchema)
43
+ @response_schema(StreamChunkSchema, 200)
44
+ @response_schema(StreamErrorSchema, 400)
45
  async def echo_stream(request: web.Request) -> web.StreamResponse:
46
  return await utils.echo_stream(request)
47
 
 
50
  def __init__(self, validator_instance=None, *args, **kwargs):
51
  super().__init__(*args, **kwargs)
52
 
53
+ self["validator"] = validator_instance if validator_instance else S1ValidatorAPI()
 
 
54
 
55
  # Add middlewares to application
56
+ self.add_routes(
57
+ [
58
+ web.post("/chat/", chat),
59
+ web.post("/echo/", echo_stream),
60
+ ]
61
+ )
62
+ self.setup_openapi()
63
  self.setup_middlewares()
64
  # TODO: Enable rewarding and other features
65
 
66
  def setup_middlewares(self):
67
+ self.middlewares.append(validation_middleware)
68
  self.middlewares.append(json_parsing_middleware)
69
  self.middlewares.append(api_key_middleware)
70
 
71
+ def setup_openapi(self):
72
+ setup_aiohttp_apispec(
73
+ app=self,
74
+ title="Prompting API",
75
+ url="/docs/swagger.json",
76
+ swagger_path="/docs",
77
+ )
78
+
79
 
80
  def main(run_aio_app=True, test=False) -> None:
81
  loop = asyncio.get_event_loop()
 
84
  # Instantiate the application with the actual validator
85
  bt.logging.info("Starting validator application.")
86
  validator_app = ValidatorApplication()
87
+ bt.logging.success("Validator app initialized successfully", validator_app)
88
 
89
  try:
90
  web.run_app(validator_app, port=port, loop=loop)