Skip to content

Commit

Permalink
Python: Fixed an issue where the schema property was missing when usi…
Browse files Browse the repository at this point in the history
…ng the OpenAPI plugin with the get method. (microsoft#8502)

Fixed an issue where the schema property was missing when using the
OpenAPI plugin with the get method.

### Motivation and Context

This Pull Request is based on Issue
microsoft#8423. When loading
an openapi plugin that uses the get method, properties other than the
"type" key in parameters.schema is missing.

### Description 

The parameters.schema includes properties other than the "type" key,
such as "description", which is used for the llm to make judgments
during function calling. Therefore, I modified it to include this
information.

[openapi
schema](https://github.com/OAI/OpenAPI-Specification/blob/4e9d2b3ec859beef08309c414f895c73a793b958/schemas/v3.0/schema.yaml#L658)

### Contribution Checklist

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄

---------

Co-authored-by: Tao Chen <[email protected]>
Co-authored-by: Evan Mattson <[email protected]>
  • Loading branch information
3 people authored Sep 17, 2024
1 parent 40e4c1c commit ac4f394
Show file tree
Hide file tree
Showing 4 changed files with 128 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(
description: str | None = None,
is_required: bool = False,
default_value: Any | None = None,
schema: str | None = None,
schema: str | dict | None = None,
response: RestApiOperationExpectedResponse | None = None,
):
"""Initialize the RestApiOperationParameter."""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,27 @@ def _parse_parameters(self, parameters: list[dict[str, Any]]):
"""Parse the parameters from the OpenAPI document."""
result: list[RestApiOperationParameter] = []
for param in parameters:
name = param["name"]
type = param["schema"]["type"]
name: str = param["name"]
if not param.get("in"):
raise PluginInitializationError(f"Parameter {name} is missing 'in' field")
if param.get("content", None) is not None:
# The schema and content fields are mutually exclusive.
raise PluginInitializationError(f"Parameter {name} cannot have a 'content' field. Expected: schema.")
location = RestApiOperationParameterLocation(param["in"])
description = param.get("description", None)
is_required = param.get("required", False)
description: str = param.get("description", None)
is_required: bool = param.get("required", False)
default_value = param.get("default", None)
schema = param.get("schema", None)
schema_type = schema.get("type", None) if schema else "string"
schema: dict[str, Any] | None = param.get("schema", None)

result.append(
RestApiOperationParameter(
name=name,
type=type,
type=schema.get("type", "string") if schema else "string",
location=location,
description=description,
is_required=is_required,
default_value=default_value,
schema=schema_type,
schema=schema if schema else {"type": "string"},
)
)
return result
Expand Down
57 changes: 57 additions & 0 deletions python/tests/unit/connectors/openapi/openapi_todo.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
openapi: 3.0.0
info:
title: Todo List API
version: 1.0.0
description: API for managing todo lists
paths:
/list:
get:
summary: Get todo list
operationId: get_todo_list
description: get todo list from specific group
parameters:
- name: listName
in: query
required: true
description: todo list group name description
schema:
type: string
description: todo list group name
responses:
"200":
description: Successful response
content:
application/json:
schema:
type: array
items:
type: object
properties:
task:
type: string
listName:
type: string

/add:
post:
summary: Add a task to a list
operationId: add_todo_list
description: add todo to specific group
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- task
properties:
task:
type: string
description: task name
listName:
type: string
description: task group name
responses:
"201":
description: Task added successfully
62 changes: 61 additions & 1 deletion python/tests/unit/connectors/openapi/test_openapi_parser.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
# Copyright (c) Microsoft. All rights reserved.

import os

import pytest

from semantic_kernel.connectors.openapi_plugin.openapi_manager import OpenApiParser
from semantic_kernel.connectors.openapi_plugin.openapi_manager import OpenApiParser, create_functions_from_openapi
from semantic_kernel.exceptions.function_exceptions import PluginInitializationError
from semantic_kernel.functions import KernelFunctionFromMethod, KernelFunctionMetadata, KernelParameterMetadata

current_dir = os.path.dirname(os.path.abspath(__file__))


def test_parse_parameters_missing_in_field():
Expand All @@ -14,6 +18,62 @@ def test_parse_parameters_missing_in_field():
parser._parse_parameters(parameters)


def test_parse_parameters_get_query():
"""Verify whether the get request query parameter can be successfully parsed"""
openapi_fcs: list[KernelFunctionFromMethod] = create_functions_from_openapi(
plugin_name="todo",
openapi_document_path=os.path.join(current_dir, "openapi_todo.yaml"),
execution_settings=None,
)

get_todo_list: list[KernelFunctionMetadata] = [
f.metadata for f in openapi_fcs if f.metadata.name == "get_todo_list"
]

assert get_todo_list

get_todo_params: list[KernelParameterMetadata] = get_todo_list[0].parameters
assert get_todo_params
assert get_todo_params[0].name == "listName"
assert get_todo_params[0].description == "todo list group name description"
assert get_todo_params[0].is_required
assert get_todo_params[0].schema_data
assert get_todo_params[0].schema_data.get("type") == "string"
assert get_todo_params[0].schema_data.get("description") == "todo list group name"


def test_parse_parameters_post_request_body():
"""Verify whether the post request body parameter can be successfully parsed"""
openapi_fcs: list[KernelFunctionFromMethod] = create_functions_from_openapi(
plugin_name="todo",
openapi_document_path=os.path.join(current_dir, "openapi_todo.yaml"),
execution_settings=None,
)

add_todo_list: list[KernelFunctionMetadata] = [
f.metadata for f in openapi_fcs if f.metadata.name == "add_todo_list"
]

assert add_todo_list

add_todo_params: list[KernelParameterMetadata] = add_todo_list[0].parameters

assert add_todo_params
assert add_todo_params[0].name == "task"
assert add_todo_params[0].description == "task name"
assert add_todo_params[0].is_required
assert add_todo_params[0].schema_data
assert add_todo_params[0].schema_data.get("type") == "string"
assert add_todo_params[0].schema_data.get("description") == "task name"

assert add_todo_params[1].name == "listName"
assert add_todo_params[1].description == "task group name"
assert not add_todo_params[1].is_required
assert add_todo_params[1].schema_data
assert add_todo_params[1].schema_data.get("type") == "string"
assert add_todo_params[1].schema_data.get("description") == "task group name"


def test_get_payload_properties_schema_none():
parser = OpenApiParser()
properties = parser._get_payload_properties("operation_id", None, [])
Expand Down

0 comments on commit ac4f394

Please sign in to comment.