forked from phoenix-oss/llama-stack-mirror
Update OpenAPI generator to add param and field documentation (#896)
We desperately need to document our APIs. This is the basic requirement of having a Spec :) This PR updates the OpenAPI generator so documentation for request parameters and object fields can be properly added to the OpenAPI specs. From there, this should get picked by Stainless, etc. ## Test Plan: Updated client-sdk (See https://github.com/meta-llama/llama-stack-client-python/pull/104) and then ran: ```bash cd tests/client-sdk LLAMA_STACK_CONFIG=../../llama_stack/templates/fireworks/run.yaml pytest -s -v inference/test_inference.py agents/test_agents.py ```
This commit is contained in:
parent
53721e91ad
commit
0d96070af9
11 changed files with 1059 additions and 2122 deletions
|
@ -36,6 +36,16 @@ from .pyopenapi.specification import Info, Server # noqa: E402
|
|||
from .pyopenapi.utility import Specification # noqa: E402
|
||||
|
||||
|
||||
def str_presenter(dumper, data):
|
||||
if data.startswith(f"/{LLAMA_STACK_API_VERSION}") or data.startswith(
|
||||
"#/components/schemas/"
|
||||
):
|
||||
style = None
|
||||
else:
|
||||
style = ">" if "\n" in data or len(data) > 40 else None
|
||||
return dumper.represent_scalar("tag:yaml.org,2002:str", data, style=style)
|
||||
|
||||
|
||||
def main(output_dir: str):
|
||||
output_dir = Path(output_dir)
|
||||
if not output_dir.exists():
|
||||
|
@ -69,7 +79,8 @@ def main(output_dir: str):
|
|||
y.sequence_dash_offset = 2
|
||||
y.width = 80
|
||||
y.allow_unicode = True
|
||||
y.explicit_start = True
|
||||
y.representer.add_representer(str, str_presenter)
|
||||
|
||||
y.dump(
|
||||
spec.get_json(),
|
||||
fp,
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
# This source code is licensed under the terms described in the LICENSE file in
|
||||
# the root directory of this source tree.
|
||||
|
||||
import collections
|
||||
import hashlib
|
||||
import ipaddress
|
||||
import typing
|
||||
from dataclasses import make_dataclass
|
||||
from typing import Any, Dict, Set, Union
|
||||
|
||||
from ..strong_typing.core import JsonType
|
||||
|
@ -276,6 +276,20 @@ class StatusResponse:
|
|||
examples: List[Any] = dataclasses.field(default_factory=list)
|
||||
|
||||
|
||||
def create_docstring_for_request(
|
||||
request_name: str, fields: List[Tuple[str, type, Any]], doc_params: Dict[str, str]
|
||||
) -> str:
|
||||
"""Creates a ReST-style docstring for a dynamically generated request dataclass."""
|
||||
lines = ["\n"] # Short description
|
||||
|
||||
# Add parameter documentation in ReST format
|
||||
for name, type_ in fields:
|
||||
desc = doc_params.get(name, "")
|
||||
lines.append(f":param {name}: {desc}")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
class ResponseBuilder:
|
||||
content_builder: ContentBuilder
|
||||
|
||||
|
@ -493,11 +507,24 @@ class Generator:
|
|||
first = next(iter(op.request_params))
|
||||
request_name, request_type = first
|
||||
|
||||
from dataclasses import make_dataclass
|
||||
|
||||
op_name = "".join(word.capitalize() for word in op.name.split("_"))
|
||||
request_name = f"{op_name}Request"
|
||||
request_type = make_dataclass(request_name, op.request_params)
|
||||
fields = [
|
||||
(
|
||||
name,
|
||||
type_,
|
||||
)
|
||||
for name, type_ in op.request_params
|
||||
]
|
||||
request_type = make_dataclass(
|
||||
request_name,
|
||||
fields,
|
||||
namespace={
|
||||
"__doc__": create_docstring_for_request(
|
||||
request_name, fields, doc_params
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
requestBody = RequestBody(
|
||||
content={
|
||||
|
@ -650,12 +677,6 @@ class Generator:
|
|||
)
|
||||
)
|
||||
|
||||
# types that are produced/consumed by operations
|
||||
type_tags = [
|
||||
self._build_type_tag(ref, schema)
|
||||
for ref, schema in self.schema_builder.schemas.items()
|
||||
]
|
||||
|
||||
# types that are emitted by events
|
||||
event_tags: List[Tag] = []
|
||||
events = get_endpoint_events(self.endpoint)
|
||||
|
@ -682,7 +703,6 @@ class Generator:
|
|||
# list all operations and types
|
||||
tags: List[Tag] = []
|
||||
tags.extend(operation_tags)
|
||||
tags.extend(type_tags)
|
||||
tags.extend(event_tags)
|
||||
for extra_tag_group in extra_tag_groups.values():
|
||||
tags.extend(extra_tag_group)
|
||||
|
@ -697,13 +717,6 @@ class Generator:
|
|||
tags=sorted(tag.name for tag in operation_tags),
|
||||
)
|
||||
)
|
||||
if type_tags:
|
||||
tag_groups.append(
|
||||
TagGroup(
|
||||
name=self.options.map("Types"),
|
||||
tags=sorted(tag.name for tag in type_tags),
|
||||
)
|
||||
)
|
||||
if event_tags:
|
||||
tag_groups.append(
|
||||
TagGroup(
|
||||
|
|
|
@ -531,6 +531,7 @@ class JsonSchemaGenerator:
|
|||
# add property docstring if available
|
||||
property_doc = property_docstrings.get(property_name)
|
||||
if property_doc:
|
||||
# print(output_name, property_doc)
|
||||
property_def.pop("title", None)
|
||||
property_def["description"] = property_doc
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue