Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python openapi plugin is not working (Error while registering Rest function students./api/controller_get: KernelFunction failed to initialize: Failed to create KernelFunctionMetadata) #10104

Closed
kkkumar2 opened this issue Jan 7, 2025 · 3 comments
Assignees
Labels
openapi Issues related to the OpenAPI function importer python Pull requests for the Python Semantic Kernel

Comments

@kkkumar2
Copy link

kkkumar2 commented Jan 7, 2025

I am just trying out semantic kernel and i am testing the native function based plugin and OPENAPI based plugin , while just native function based plugin is working fine but OPENAPI based plugin is throwing an error . Upon debugging i found out that the issue was function name is having "/" which is not supported by the regex pattern in class KernelFunctionMetadata (name: str = Field(..., pattern=FUNCTION_NAME_REGEX)). API can have "/" in their paths and since i am directly using swagger.json function name is inferrred from paths only. for this scenario function name inferred is /api/controller_get.

FUNCTION_NAME_REGEX = r"^[0-9A-Za-z_]+$"

Code:

import asyncio

from semantic_kernel import Kernel
from semantic_kernel.utils.logging import setup_logging
from semantic_kernel.functions import kernel_function
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments

from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
    AzureChatPromptExecutionSettings,
)
import logging
from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import OpenAPIFunctionExecutionParameters

from typing import Annotated
from semantic_kernel.functions import kernel_function

class LightsPlugin:
    lights = [
        {"id": 1, "name": "Table Lamp", "is_on": False},
        {"id": 2, "name": "Porch light", "is_on": False},
        {"id": 3, "name": "Chandelier", "is_on": True},
    ]

    @kernel_function(
        name="get_lights",
        description="Gets a list of lights and their current state",
    )
    def get_state(
        self,
    ) -> Annotated[str, "the output is a string"]:
        """Gets a list of lights and their current state."""
        return self.lights

    @kernel_function(
        name="change_state",
        description="Changes the state of the light",
    )
    def change_state(
        self,
        id: int,
        is_on: bool,
    ) -> Annotated[str, "the output is a string"]:
        """Changes the state of the light."""
        for light in self.lights:
            if light["id"] == id:
                light["is_on"] = is_on
                return light
        return None

async def main():
    # Initialize the kernel
    kernel = Kernel()

    # Add Azure OpenAI chat completion
    chat_completion = AzureChatCompletion(
        deployment_name="",
        api_key="",
        base_url="",
    )
    kernel.add_service(chat_completion)

    # Set the logging level for  semantic_kernel.kernel to DEBUG.
    setup_logging()
    logging.getLogger("kernel").setLevel(logging.DEBUG)

    # Add a plugin (the LightsPlugin class is defined below)
    # kernel.add_plugin(
    #     LightsPlugin(),
    #     plugin_name="Lights",
    # )


    kernel.add_plugin_from_openapi(
       plugin_name="students",
       openapi_document_path="./openapi.json",)
       #openapi_parsed_spec=json_data,
       #execution_settings=OpenAPIFunctionExecutionParameters(enable_payload_namespacing=True,))
    
    # Enable planning
    execution_settings = AzureChatPromptExecutionSettings()
    execution_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()

    # Create a history of the conversation
    history = ChatHistory()

    # Initiate a back-and-forth chat
    userInput = None
    while True:
        # Collect user input
        userInput = input("User > ")

        # Terminate the loop if the user says "exit"
        if userInput == "exit":
            break

        # Add user input to the history
        history.add_user_message(userInput)

        # Get the response from the AI
        result = await chat_completion.get_chat_message_content(
            chat_history=history,
            settings=execution_settings,
            kernel=kernel,
        )

        # Print the results
        print("Assistant > " + str(result))

        # Add the message from the agent to the chat history
        history.add_message(result)

# Run the main function
if __name__ == "__main__":
    asyncio.run(main())

OpenAPI.json

{"openapi": "3.0.1", "info": {"title": "WebApplication1", "version": "1.0"}, "paths": {"/api/controller": {"get": {"tags": ["Student"], "summary": "Gets the list of students.", "description": "This endpoint returns a list of student objects with their ID and name.", "responses": {"200": {"description": "OK", "content": {"text/plain": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/Student"}}}, "application/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/Student"}}}, "text/json": {"schema": {"type": "array", "items": {"$ref": "#/components/schemas/Student"}}}}}, "500": {"description": "Internal Server Error"}}}}}, "components": {"schemas": {"Student": {"type": "object", "properties": {"id": {"type": "integer", "format": "int32"}, "name": {"type": "string", "nullable": true}}, "additionalProperties": false}}}}

Error:

[2025-01-07 17:37:20 - semantic_kernel.connectors.openapi_plugin.openapi_manager:98 - ERROR] Error while registering Rest function students./api/controller_get: KernelFunction failed to initialize: Failed to create KernelFunctionMetadata
Traceback (most recent call last):
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\functions\kernel_function_from_method.py", line 69, in __init__
    metadata = KernelFunctionMetadata(
               ^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\pydantic\main.py", line 212, in __init__
    validated_self = self.__pydantic_validator__.validate_python(data, self_instance=self)
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
pydantic_core._pydantic_core.ValidationError: 1 validation error for KernelFunctionMetadata
name
  String should match pattern '^[0-9A-Za-z_]+$' [type=string_pattern_mismatch, input_value='/api/controller_get', input_type=str]
    For further information visit https://errors.pydantic.dev/2.9/v/string_pattern_mismatch

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\connectors\openapi_plugin\openapi_manager.py", line 87, in create_functions_from_openapi  
    kernel_function = _create_function_from_operation(
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\connectors\openapi_plugin\openapi_manager.py", line 195, in _create_function_from_operation
    return KernelFunctionFromMethod(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\functions\kernel_function_from_method.py", line 81, in __init__
    raise FunctionInitializationError("Failed to create KernelFunctionMetadata") from exc
semantic_kernel.exceptions.function_exceptions.FunctionInitializationError: KernelFunction failed to initialize: Failed to create KernelFunctionMetadata

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\semantic.py", line 118, in <module>
    asyncio.run(main())
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\asyncio\runners.py", line 190, in run
    return runner.run(main)
           ^^^^^^^^^^^^^^^^
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\asyncio\runners.py", line 118, in run
    return self._loop.run_until_complete(task)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Program Files\WindowsApps\PythonSoftwareFoundation.Python.3.11_3.11.2544.0_x64__qbz5n2kfra8p0\Lib\asyncio\base_events.py", line 654, in run_until_complete
    return future.result()
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\semantic.py", line 77, in main
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\semantic.py", line 77, in main 
    kernel.add_plugin_from_openapi(
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\functions\kernel_function_extension.py", line 232, in add_plugin_from_openapi
    KernelPlugin.from_openapi(
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\functions\kernel_plugin.py", line 387, in from_openapi
    functions=create_functions_from_openapi(  # type: ignore
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\v-mkanagaraj\OneDrive - Microsoft\Documents\Practise\sem_env\Lib\site-packages\semantic_kernel\connectors\openapi_plugin\openapi_manager.py", line 99, in create_functions_from_openapi  
    raise FunctionExecutionException(error_msg) from ex
semantic_kernel.exceptions.function_exceptions.FunctionExecutionException: Error while registering Rest function students./api/controller_get: KernelFunction failed to initialize: Failed to create KernelFunctionMetadata
python version 3.11.9
semantic-kernel==1.17.1
@moonbox3
Copy link
Contributor

moonbox3 commented Jan 8, 2025

Hi @kkkumar2, thanks for filing the issue. As you've found, the function name violates our function name regex requirement. The default logic in OpenApiParser sets:

operationId = details.get("operationId", path + "_" + requestMethod)

But if path is "/api/controller", you end up with operationId == "/api/controller_get", which is invalid by the time we get to KernelFunctionMetadata.

Are you able to add operationIds instead of us trying to build it up from the path, which is currently breaking?

Something like:

{
  "paths": {
    "/api/controller": {
      "get": {
        "operationId": "api_controller_get",
        ...
      }
    }
  }
}

This would get you unblocked and allow the parser to handle your swagger spec.

@moonbox3 moonbox3 added the openapi Issues related to the OpenAPI function importer label Jan 8, 2025
@kkkumar2
Copy link
Author

kkkumar2 commented Jan 8, 2025

yes @moonbox3 , adding operationId in json file is working properly. how much weightage does function_name in kernel function has ? suppose if i set operationid (function_name) to GUID , does it have any impact while function calling ? I have tested it with smaller set of functions and it seems to be working fine when i set GUID to operationId

@kkkumar2 kkkumar2 closed this as completed Jan 8, 2025
@moonbox3
Copy link
Contributor

moonbox3 commented Jan 8, 2025

Hi @kkkumar2, we use the operationId as part of the function’s name.

@kernel_function(
description=operation.summary if operation.summary else operation.description,
name=operation.id,
)
async def run_openapi_operation(**kwargs: dict[str, Any]) -> str:
...

I’d recommend keeping that in mind as function calling may work with a small amount of APIs, but it’s probably better to make them more human readable (and thus semantically meaningful).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
openapi Issues related to the OpenAPI function importer python Pull requests for the Python Semantic Kernel
Projects
Status: Bug
Development

No branches or pull requests

4 participants